use crate::completion::path::expand_tilde;
use crate::config::RSourceStatus;
use crate::external::formatter;
use crate::pager::HistoryDbMode;
use reedline::{History, SqliteBackedHistory};
use std::path::PathBuf;
use super::shell::confirm_action;
use super::state::PromptRuntimeConfig;
use super::{ARF_PREFIX, arf_println};
pub enum MetaCommandResult {
Handled,
Exit,
Unknown(String),
ShellExecuted,
Restart(Option<String>),
ShowHelpBrowser(String),
ShowSessionInfo,
ShowChangelog,
ShowHistoryBrowser { path: PathBuf, mode: HistoryDbMode },
ShowHistorySchema,
}
#[allow(clippy::too_many_arguments)]
pub fn process_meta_command(
input: &str,
prompt_config: &mut PromptRuntimeConfig,
r_history_path: &Option<PathBuf>,
shell_history_path: &Option<PathBuf>,
r_source_status: &RSourceStatus,
dir_stack: &mut Vec<PathBuf>,
history_session_id: Option<i64>,
) -> Option<MetaCommandResult> {
let trimmed = input.trim();
if !trimmed.starts_with(':') {
return None;
}
let parts: Vec<&str> = trimmed[1..].split_whitespace().collect();
let cmd = parts.first().copied().unwrap_or("");
match cmd {
"reprex" => {
prompt_config.toggle_reprex();
if prompt_config.is_reprex_enabled() {
if prompt_config.is_autoformat_enabled() {
println!(
"# Reprex mode enabled (comment: {:?}, auto-format: on)",
prompt_config.reprex_comment
);
} else {
arf_println!(
"Reprex mode enabled (comment: {:?})",
prompt_config.reprex_comment
);
}
} else {
arf_println!("Reprex mode disabled");
}
Some(MetaCommandResult::Handled)
}
"autoformat" | "format" => {
if prompt_config.is_autoformat_enabled() {
prompt_config.toggle_autoformat();
arf_println!("Autoformat disabled");
} else {
if formatter::is_formatter_available() {
prompt_config.toggle_autoformat();
if prompt_config.is_reprex_enabled() {
arf_println!("Autoformat enabled");
} else {
arf_println!("Autoformat enabled (activate reprex mode to use)");
}
} else {
arf_println!(
"Error: Cannot enable autoformat - Air CLI ('air' command) not found in PATH."
);
arf_println!("Install Air CLI from https://github.com/posit-dev/air");
}
}
Some(MetaCommandResult::Handled)
}
"shell" => {
prompt_config.set_shell(true);
arf_println!("Shell mode enabled. Type :r to return to R.");
Some(MetaCommandResult::Handled)
}
"r" | "R" => {
if prompt_config.is_shell_enabled() {
prompt_config.set_shell(false);
arf_println!("Returned to R mode.");
} else {
arf_println!("Already in R mode.");
}
Some(MetaCommandResult::Handled)
}
"system" => {
let shell_cmd = trimmed[1..].strip_prefix("system").unwrap_or("").trim();
if shell_cmd.is_empty() {
arf_println!("Usage: :system <command>");
} else {
if let Some(hint) = dir_command_hint(shell_cmd) {
arf_println!("{}", hint);
}
super::shell::execute_shell_command(shell_cmd);
}
Some(MetaCommandResult::ShellExecuted)
}
"restart" | "restart!" => {
let force = cmd == "restart!";
if force
|| confirm_action(&format!(
"{} Restart R session? Current session will be lost.",
ARF_PREFIX
))
{
arf_println!("Restarting R session...");
Some(MetaCommandResult::Restart(None))
} else {
arf_println!("Restart cancelled.");
Some(MetaCommandResult::Handled)
}
}
"switch" | "switch!" => {
if !r_source_status.rig_enabled() {
arf_println!("Error: :switch requires rig to be available at startup.");
arf_println!(
r#"Start arf with r_source = "auto" (with rig installed) or r_source = "rig"."#
);
return Some(MetaCommandResult::Handled);
}
let version = parts.get(1).map(|s| s.to_string());
if version.is_none() {
arf_println!("Usage: :{cmd} <version>");
arf_println!("Example: :{cmd} 4.4 or :{cmd} release");
return Some(MetaCommandResult::Handled);
}
let force = cmd == "switch!";
let ver = version.as_ref().unwrap();
if force || confirm_action(&format!("Restart with R {}?", ver)) {
arf_println!("Restarting with R {}...", ver);
Some(MetaCommandResult::Restart(version))
} else {
arf_println!("Switch cancelled.");
Some(MetaCommandResult::Handled)
}
}
"history" => {
let subcmd = parts.get(1).copied().unwrap_or("");
match subcmd {
"browse" => {
let target = parts.get(2).copied().unwrap_or("");
process_history_browse(
r_history_path,
shell_history_path,
target,
prompt_config.is_shell_enabled(),
)
}
"clear" => {
let target = parts.get(2).copied().unwrap_or("");
process_history_clear(
r_history_path,
shell_history_path,
target,
prompt_config.is_shell_enabled(),
)
}
"schema" => Some(MetaCommandResult::ShowHistorySchema),
"" => {
arf_println!("Usage: :history <subcommand>");
println!("# browse - Browse and manage command history");
println!("# clear - Clear command history");
println!("# schema - Display database schema and R examples");
Some(MetaCommandResult::Handled)
}
_ => {
arf_println!(
"Unknown history subcommand: {}. Use :history for help",
subcmd
);
Some(MetaCommandResult::Handled)
}
}
}
"help" | "h" => {
let query = parts.get(1..).map(|p| p.join(" ")).unwrap_or_default();
Some(MetaCommandResult::ShowHelpBrowser(query))
}
"info" | "session" => Some(MetaCommandResult::ShowSessionInfo),
"changelog" => Some(MetaCommandResult::ShowChangelog),
"cd" => {
let path_arg = trimmed[1..].strip_prefix("cd").unwrap_or("").trim();
match meta_cd(path_arg) {
Ok(cwd) => arf_println!("{}", cwd.display()),
Err(e) => arf_println!("cd: {}", e),
}
Some(MetaCommandResult::Handled)
}
"pushd" => {
let path_arg = trimmed[1..].strip_prefix("pushd").unwrap_or("").trim();
match meta_pushd(dir_stack, path_arg) {
Ok(cwd) => arf_println!("{}", cwd.display()),
Err(e) => arf_println!("pushd: {}", e),
}
Some(MetaCommandResult::Handled)
}
"popd" => {
match meta_popd(dir_stack) {
Ok(cwd) => arf_println!("{}", cwd.display()),
Err(e) => arf_println!("popd: {}", e),
}
Some(MetaCommandResult::Handled)
}
"ipc" => {
let subcmd = parts.get(1).copied().unwrap_or("status");
match subcmd {
"start" => match crate::ipc::start_server(None, None, history_session_id) {
Ok(session) => {
arf_println!("IPC server started: {}", session.socket_path)
}
Err(e) => arf_println!("Failed to start IPC server: {}", e),
},
"stop" => {
crate::ipc::stop_server();
arf_println!("IPC server stopped.");
}
"status" => {
let sessions = crate::ipc::session::list_sessions();
let my_pid = std::process::id();
let my_session = sessions.iter().find(|s| s.pid == my_pid);
if let Some(session) = my_session {
arf_println!("IPC server is running.");
println!("# Socket: {}", session.socket_path);
println!("# PID: {}", session.pid);
} else {
arf_println!(
"IPC server is not running. Use :ipc start or --with-ipc flag."
);
}
}
_ => {
arf_println!("Unknown :ipc subcommand. Available: start, stop, status");
}
}
Some(MetaCommandResult::Handled)
}
"commands" | "cmds" => {
arf_println!("Available commands:");
println!("# :help - Search R help");
println!("# :info - Show session information");
println!("# :shell - Enter shell mode (input goes to system shell)");
println!("# :r - Return to R mode (from shell mode)");
println!("# :system <cmd> - Execute a single system command");
println!("# :cd <path> - Change working directory");
println!("# :pushd <path> - Push directory and change to it");
println!("# :popd - Pop directory from stack");
println!("# :reprex - Toggle reprex mode");
println!(
"# :autoformat - Toggle auto-formatting in reprex mode (requires Air CLI)"
);
println!("# :history - History management (browse, clear, schema)");
println!("# :restart - Restart R session");
println!("# :restart! - Restart without confirmation");
println!("# :switch <ver> - Restart with different R version (requires rig)");
println!("# :switch! <ver> - Switch without confirmation");
println!("# :ipc - IPC server management (start, stop, status)");
println!("# :changelog - Show arf changelog");
println!("# :commands - Show this list");
println!("# :quit - Exit arf");
Some(MetaCommandResult::Handled)
}
"quit" | "exit" => Some(MetaCommandResult::Exit),
"" => {
arf_println!("Type :commands for available commands");
Some(MetaCommandResult::Handled)
}
_ => Some(MetaCommandResult::Unknown(cmd.to_string())),
}
}
fn process_history_browse(
r_history_path: &Option<PathBuf>,
shell_history_path: &Option<PathBuf>,
target: &str,
is_shell_mode: bool,
) -> Option<MetaCommandResult> {
let (mode, path) = match target {
"" => {
if is_shell_mode {
(HistoryDbMode::Shell, shell_history_path.as_ref())
} else {
(HistoryDbMode::R, r_history_path.as_ref())
}
}
"r" | "R" => (HistoryDbMode::R, r_history_path.as_ref()),
"shell" => (HistoryDbMode::Shell, shell_history_path.as_ref()),
_ => {
arf_println!("Unknown target: {}. Use r or shell.", target);
return Some(MetaCommandResult::Handled);
}
};
let Some(db_path) = path else {
arf_println!("History is disabled for {} mode.", mode.display_name());
return Some(MetaCommandResult::Handled);
};
Some(MetaCommandResult::ShowHistoryBrowser {
path: db_path.clone(),
mode,
})
}
fn process_history_clear(
r_history_path: &Option<PathBuf>,
shell_history_path: &Option<PathBuf>,
target: &str,
is_shell_mode: bool,
) -> Option<MetaCommandResult> {
let clear_target = match target {
"" => {
if is_shell_mode { "shell" } else { "r" }
}
"r" | "R" => "r",
"shell" => "shell",
"all" => "all",
_ => {
arf_println!("Unknown target: {}. Use r, shell, or all.", target);
return Some(MetaCommandResult::Handled);
}
};
let paths_to_clear: Vec<(&str, &PathBuf)> = match clear_target {
"r" => r_history_path
.as_ref()
.map(|p| vec![("R", p)])
.unwrap_or_default(),
"shell" => shell_history_path
.as_ref()
.map(|p| vec![("Shell", p)])
.unwrap_or_default(),
"all" => {
let mut paths = Vec::new();
if let Some(p) = r_history_path.as_ref() {
paths.push(("R", p));
}
if let Some(p) = shell_history_path.as_ref() {
paths.push(("Shell", p));
}
paths
}
_ => unreachable!(),
};
if paths_to_clear.is_empty() {
arf_println!("History is disabled.");
return Some(MetaCommandResult::Handled);
}
let mut total_count = 0i64;
let mut counts: Vec<(&str, i64)> = Vec::new();
for (name, path) in &paths_to_clear {
match SqliteBackedHistory::with_file((*path).clone(), None, None) {
Ok(history) => {
if let Ok(count) = history.count_all() {
counts.push((name, count));
total_count += count;
}
}
Err(_) => {
counts.push((name, 0));
}
}
}
if total_count == 0 {
arf_println!("History is already empty.");
return Some(MetaCommandResult::Handled);
}
if counts.len() == 1 {
arf_println!("{} history: {} entries", counts[0].0, counts[0].1);
} else {
for (name, count) in &counts {
arf_println!("{} history: {} entries", name, count);
}
arf_println!("Total: {} entries", total_count);
}
let prompt = format!("{} Clear {} history entries?", ARF_PREFIX, total_count);
if !confirm_action(&prompt) {
arf_println!("Cancelled.");
return Some(MetaCommandResult::Handled);
}
let mut cleared_count = 0i64;
for (name, path) in &paths_to_clear {
match SqliteBackedHistory::with_file((*path).clone(), None, None) {
Ok(mut history) => {
if let Ok(count) = history.count_all()
&& count > 0
{
if let Err(e) = history.clear() {
arf_println!("Failed to clear {} history: {}", name, e);
} else {
cleared_count += count;
}
}
}
Err(_) => {
}
}
}
arf_println!("Cleared {} history entries.", cleared_count);
Some(MetaCommandResult::Handled)
}
pub(crate) fn meta_cd(path_arg: &str) -> Result<PathBuf, String> {
let target = if path_arg.is_empty() {
dirs::home_dir().ok_or_else(|| "Cannot determine home directory".to_string())?
} else {
PathBuf::from(expand_tilde(path_arg))
};
std::env::set_current_dir(&target).map_err(|e| format!("{}: {}", target.display(), e))?;
std::env::current_dir().map_err(|e| e.to_string())
}
pub(crate) fn meta_pushd(dir_stack: &mut Vec<PathBuf>, path_arg: &str) -> Result<PathBuf, String> {
if path_arg.is_empty() {
return Err("Usage: :pushd <path>".to_string());
}
let current = std::env::current_dir().map_err(|e| e.to_string())?;
let new_dir = meta_cd(path_arg)?;
dir_stack.push(current);
Ok(new_dir)
}
pub(crate) fn meta_popd(dir_stack: &mut Vec<PathBuf>) -> Result<PathBuf, String> {
let target = dir_stack
.last()
.cloned()
.ok_or_else(|| "Directory stack is empty".to_string())?;
std::env::set_current_dir(&target).map_err(|e| format!("{}", e))?;
dir_stack.pop();
std::env::current_dir().map_err(|e| e.to_string())
}
pub(crate) fn dir_command_hint(shell_cmd: &str) -> Option<&'static str> {
match shell_cmd.split_whitespace().next()? {
"cd" => Some("Hint: Use the :cd meta command instead to change directory."),
"pushd" => Some("Hint: Use the :pushd meta command instead to change directory."),
"popd" => Some("Hint: Use the :popd meta command instead to restore directory."),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::prompt::PromptFormatter;
fn create_test_prompt_config() -> PromptRuntimeConfig {
PromptRuntimeConfig::builder(PromptFormatter::default(), "r> ", "+ ", "[bash] $ ").build()
}
fn default_r_source_status() -> RSourceStatus {
RSourceStatus::Path
}
fn call_meta(
input: &str,
config: &mut PromptRuntimeConfig,
r_history_path: &Option<PathBuf>,
shell_history_path: &Option<PathBuf>,
status: &RSourceStatus,
) -> Option<MetaCommandResult> {
let mut dir_stack = Vec::new();
process_meta_command(
input,
config,
r_history_path,
shell_history_path,
status,
&mut dir_stack,
None,
)
}
#[test]
fn test_process_meta_command_not_meta() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta("print(x)", &mut config, &None, &None, &status);
assert!(result.is_none());
}
#[test]
fn test_process_meta_command_reprex_toggle() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
assert!(!config.is_reprex_enabled());
let result = call_meta(":reprex", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(config.is_reprex_enabled());
let result = call_meta(":reprex", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(!config.is_reprex_enabled());
}
#[test]
fn test_process_meta_command_commands() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":commands", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
let result = call_meta(":cmds", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
}
#[test]
fn test_process_meta_command_info() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":info", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::ShowSessionInfo)));
let result = call_meta(":session", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::ShowSessionInfo)));
}
#[test]
fn test_process_meta_command_quit() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":quit", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Exit)));
let result = call_meta(":exit", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Exit)));
}
#[test]
fn test_process_meta_command_unknown() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":unknown", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Unknown(_))));
}
#[test]
fn test_process_meta_command_empty_colon() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
}
#[test]
fn test_process_meta_command_with_whitespace() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(" :reprex ", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(config.is_reprex_enabled());
}
#[test]
fn test_process_meta_command_shell_enter() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
assert!(!config.is_shell_enabled());
let result = call_meta(":shell", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(config.is_shell_enabled());
}
#[test]
fn test_process_meta_command_shell_exit_with_r() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
config.set_shell(true);
assert!(config.is_shell_enabled());
let result = call_meta(":r", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(!config.is_shell_enabled());
}
#[test]
fn test_process_meta_command_shell_exit_with_uppercase_r() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
config.set_shell(true);
assert!(config.is_shell_enabled());
let result = call_meta(":R", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(!config.is_shell_enabled());
}
#[test]
fn test_process_meta_command_r_when_not_in_shell() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
assert!(!config.is_shell_enabled());
let result = call_meta(":r", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(!config.is_shell_enabled()); }
#[test]
fn test_process_meta_command_system() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":system echo hello", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::ShellExecuted)));
}
#[test]
fn test_process_meta_command_system_empty() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":system", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::ShellExecuted)));
}
#[test]
fn test_process_meta_command_switch_requires_rig() {
let mut config = create_test_prompt_config();
let status_path = RSourceStatus::Path;
let result = call_meta(":switch 4.4", &mut config, &None, &None, &status_path);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
let status_rig = RSourceStatus::Rig {
version: "4.4.0".to_string(),
};
let result = call_meta(":switch", &mut config, &None, &None, &status_rig);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
}
#[test]
fn test_meta_cd_relative_path() {
let _guard = crate::test_utils::lock_cwd();
let tmp = tempfile::tempdir().unwrap();
let subdir = tmp.path().join("sub");
std::fs::create_dir(&subdir).unwrap();
std::env::set_current_dir(tmp.path()).unwrap();
let result = meta_cd("sub");
assert!(result.is_ok());
assert!(result.unwrap().ends_with("sub"));
}
#[test]
fn test_meta_cd_absolute_path() {
let _guard = crate::test_utils::lock_cwd();
let tmp = tempfile::tempdir().unwrap();
let result = meta_cd(&tmp.path().to_string_lossy());
assert!(result.is_ok());
}
#[test]
fn test_meta_cd_tilde() {
let _guard = crate::test_utils::lock_cwd();
let result = meta_cd("~");
assert!(result.is_ok());
if let Some(home) = dirs::home_dir() {
assert_eq!(
result.unwrap().canonicalize().ok(),
home.canonicalize().ok()
);
}
}
#[test]
fn test_meta_cd_no_args() {
let _guard = crate::test_utils::lock_cwd();
let result = meta_cd("");
assert!(result.is_ok());
if let Some(home) = dirs::home_dir() {
assert_eq!(
result.unwrap().canonicalize().ok(),
home.canonicalize().ok()
);
}
}
#[test]
fn test_meta_cd_nonexistent() {
let result = meta_cd("/nonexistent_path_12345");
assert!(result.is_err());
}
#[test]
fn test_meta_pushd_popd() {
let _guard = crate::test_utils::lock_cwd();
let tmp = tempfile::tempdir().unwrap();
let mut dir_stack = Vec::new();
std::env::set_current_dir(tmp.path()).unwrap();
let subdir = tmp.path().join("sub");
std::fs::create_dir(&subdir).unwrap();
let result = meta_pushd(&mut dir_stack, "sub");
assert!(result.is_ok());
assert_eq!(dir_stack.len(), 1);
assert!(std::env::current_dir().unwrap().ends_with("sub"));
let result = meta_popd(&mut dir_stack);
assert!(result.is_ok());
assert!(dir_stack.is_empty());
assert_eq!(
std::env::current_dir().unwrap().canonicalize().ok(),
tmp.path().canonicalize().ok()
);
}
#[test]
fn test_meta_popd_empty_stack() {
let mut dir_stack: Vec<PathBuf> = Vec::new();
let result = meta_popd(&mut dir_stack);
assert!(result.is_err());
assert!(result.unwrap_err().contains("empty"));
}
#[test]
fn test_meta_pushd_no_args_returns_error() {
let mut dir_stack: Vec<PathBuf> = Vec::new();
let result = meta_pushd(&mut dir_stack, "");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Usage"));
assert!(dir_stack.is_empty());
}
#[test]
fn test_meta_pushd_saves_previous() {
let _guard = crate::test_utils::lock_cwd();
let tmp = tempfile::tempdir().unwrap();
let subdir = tmp.path().join("sub");
std::fs::create_dir(&subdir).unwrap();
let mut dir_stack = Vec::new();
std::env::set_current_dir(tmp.path()).unwrap();
let before_pushd = std::env::current_dir().unwrap();
let _ = meta_pushd(&mut dir_stack, "sub");
assert_eq!(dir_stack.len(), 1);
assert_eq!(
dir_stack[0].canonicalize().ok(),
before_pushd.canonicalize().ok()
);
}
#[test]
fn test_process_meta_command_cd() {
let _guard = crate::test_utils::lock_cwd();
let tmp = tempfile::tempdir().unwrap();
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let mut dir_stack = Vec::new();
let result = process_meta_command(
&format!(":cd {}", tmp.path().display()),
&mut config,
&None,
&None,
&status,
&mut dir_stack,
None,
);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
}
#[test]
fn test_process_meta_command_pushd_popd() {
let _guard = crate::test_utils::lock_cwd();
let tmp = tempfile::tempdir().unwrap();
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let mut dir_stack = Vec::new();
let result = process_meta_command(
&format!(":pushd {}", tmp.path().display()),
&mut config,
&None,
&None,
&status,
&mut dir_stack,
None,
);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert_eq!(dir_stack.len(), 1);
let result = process_meta_command(
":popd",
&mut config,
&None,
&None,
&status,
&mut dir_stack,
None,
);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
assert!(dir_stack.is_empty());
}
#[test]
fn test_dir_command_hint_cd() {
assert!(dir_command_hint("cd /tmp").unwrap().contains(":cd"));
}
#[test]
fn test_dir_command_hint_pushd() {
assert!(dir_command_hint("pushd /tmp").unwrap().contains(":pushd"));
}
#[test]
fn test_dir_command_hint_popd() {
assert!(dir_command_hint("popd").unwrap().contains(":popd"));
}
#[test]
fn test_dir_command_hint_other() {
assert!(dir_command_hint("ls -la").is_none());
assert!(dir_command_hint("echo cd").is_none());
}
#[test]
fn test_dir_command_hint_empty() {
assert!(dir_command_hint("").is_none());
}
#[test]
fn test_process_meta_command_restart_force() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":restart!", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Restart(None))));
}
#[test]
fn test_process_meta_command_restart_force_with_whitespace() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(" :restart! ", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Restart(None))));
}
#[test]
fn test_process_meta_command_switch_force() {
let mut config = create_test_prompt_config();
let status_rig = RSourceStatus::Rig {
version: "4.4.0".to_string(),
};
let result = call_meta(":switch! 4.4", &mut config, &None, &None, &status_rig);
assert!(matches!(result, Some(MetaCommandResult::Restart(Some(ref v))) if v == "4.4"));
}
#[test]
fn test_process_meta_command_switch_force_no_version() {
let mut config = create_test_prompt_config();
let status_rig = RSourceStatus::Rig {
version: "4.4.0".to_string(),
};
let result = call_meta(":switch!", &mut config, &None, &None, &status_rig);
assert!(matches!(result, Some(MetaCommandResult::Handled)));
}
#[test]
fn test_process_meta_command_unknown_with_bang() {
let mut config = create_test_prompt_config();
let status = default_r_source_status();
let result = call_meta(":shell!", &mut config, &None, &None, &status);
assert!(matches!(result, Some(MetaCommandResult::Unknown(_))));
}
}