#![warn(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::cargo)]
#![allow(clippy::manual_let_else)]
#![allow(clippy::multiple_crate_versions)]
#![allow(clippy::question_mark)] #![allow(unknown_lints)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
#![warn(rust_2018_idioms)]
#![warn(rust_2021_compatibility)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_alias))]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
mod readme {}
use std::env;
use std::fs;
use std::path::PathBuf;
use bstr::ByteSlice;
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum EditMode {
#[default]
Emacs,
Vi,
}
#[cfg(feature = "rustyline")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustyline")))]
impl From<EditMode> for rustyline::config::EditMode {
fn from(mode: EditMode) -> Self {
match mode {
EditMode::Emacs => Self::Emacs,
EditMode::Vi => Self::Vi,
}
}
}
#[must_use]
pub fn rl_read_init_file() -> Option<Vec<u8>> {
if let Some(inputrc) = env::var_os("INPUTRC") {
return fs::read(inputrc).ok();
}
let home_dir = home_dir();
if let Some(ref home_dir) = home_dir {
let inputrc = home_dir.join(".inputrc");
if let Ok(content) = fs::read(inputrc) {
return Some(content);
}
}
if let Ok(content) = fs::read("/etc/inputrc") {
return Some(content);
}
if cfg!(windows) {
if let Some(home_dir) = home_dir {
let inputrc = home_dir.join("_inputrc");
if let Ok(content) = fs::read(inputrc) {
return Some(content);
}
}
}
None
}
#[cfg(not(any(unix, windows)))]
fn home_dir() -> Option<PathBuf> {
None
}
#[cfg(unix)]
fn home_dir() -> Option<PathBuf> {
#[allow(deprecated)]
env::home_dir()
}
#[cfg(windows)]
fn home_dir() -> Option<PathBuf> {
use known_folders::{get_known_folder_path, KnownFolder};
get_known_folder_path(KnownFolder::Profile)
}
#[must_use]
pub fn get_readline_edit_mode(contents: impl AsRef<[u8]>) -> Option<EditMode> {
fn inner(contents: &[u8]) -> Option<EditMode> {
let mut edit_mode = None; for line in contents.lines() {
let line = trim_whitespace_front(line);
if matches!(line.first(), Some(b'#') | None) {
continue;
}
let line = match line.strip_prefix(b"set") {
Some(rest) => rest,
None => continue,
};
let line = trim_whitespace_front(line);
let line = match line {
[b'e' | b'E', b'd' | b'D', b'i' | b'I', b't' | b'T', b'i' | b'I', b'n' | b'N', b'g' | b'G', b'-', b'm' | b'M', b'o' | b'O', b'd' | b'D', b'e' | b'E', rest @ ..] => {
rest
}
_ => continue,
};
let line = trim_whitespace_front(line);
match line {
[b'v' | b'V', b'i' | b'I'] => {
edit_mode = Some(EditMode::Vi);
}
[b'e' | b'E', b'm' | b'M', b'a' | b'A', b'c' | b'C', b's' | b'S'] => {
edit_mode = Some(EditMode::Emacs);
}
[b'v' | b'V', b'i' | b'I', next, ..] if posix_space::is_space(*next) => {
edit_mode = Some(EditMode::Vi);
}
[b'e' | b'E', b'm' | b'M', b'a' | b'A', b'c' | b'C', b's' | b'S', next, ..]
if posix_space::is_space(*next) =>
{
edit_mode = Some(EditMode::Emacs);
}
_ => {}
}
}
edit_mode
}
inner(contents.as_ref())
}
fn trim_whitespace_front(mut s: &[u8]) -> &[u8] {
loop {
if let Some((&head, tail)) = s.split_first() {
if posix_space::is_space(head) {
s = tail;
continue;
}
}
break s;
}
}
#[cfg(test)]
mod tests {
use super::{get_readline_edit_mode, EditMode};
#[test]
fn test_default_edit_mode_is_emacs() {
assert_eq!(EditMode::default(), EditMode::Emacs);
}
#[test]
#[cfg(feature = "rustyline")]
fn test_edit_mode_rustyline_into() {
assert_eq!(rustyline::config::EditMode::Emacs, EditMode::Emacs.into());
assert_eq!(rustyline::config::EditMode::Vi, EditMode::Vi.into());
}
#[test]
fn test_get_readline_edit_mode_vi_mode() {
let config = "set editing-mode vi";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_emacs_mode() {
let config = "set editing-mode emacs";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Emacs));
}
#[test]
fn test_get_readline_edit_mode_parse_empty_and_blank_lines() {
let test_cases = [
"",
" ",
"\t\t\t",
" \n ",
"\n",
"\r\n",
" \r\n ",
];
for contents in test_cases {
assert_eq!(get_readline_edit_mode(contents), None);
}
}
#[test]
fn test_get_readline_edit_mode_whitespace_only_lines() {
let contents = "
\t
\n
\r
";
assert_eq!(get_readline_edit_mode(contents), None);
}
#[test]
fn test_get_readline_edit_mode_empty_contents() {
let contents = "";
assert_eq!(get_readline_edit_mode(contents), None);
}
#[test]
fn test_get_readline_edit_mode_no_set_directive() {
let contents = "editing-mode vi";
assert_eq!(get_readline_edit_mode(contents), None);
}
#[test]
fn test_get_readline_edit_mode_comment_lines() {
let contents = "
# This is a comment
# set editing-mode vi
# set editing-mode emacs
";
assert_eq!(get_readline_edit_mode(contents), None);
}
#[test]
fn test_get_readline_edit_mode_set_editing_mode_with_space_before_variable_name() {
let test_cases = [
("set editing-mode vi", EditMode::Vi),
("set editing-mode emacs", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_edit_mode_set_editing_mode_with_space_after_variable_name() {
let test_cases = [
("set editing-mode vi", EditMode::Vi),
("set editing-mode emacs", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_edit_mode_excess_whitespace() {
let test_cases = [
("set editing-mode \t vi \t \r\n", EditMode::Vi),
("set editing-mode \t emacs \t \r\n", EditMode::Emacs),
("set editing-mode vi \t \n", EditMode::Vi),
("set editing-mode emacs \t \n", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_edit_mode_ignore_trailing_garbage() {
let test_cases = [
("set editing-mode vi 1234", EditMode::Vi),
("set editing-mode emacs 1234", EditMode::Emacs),
("set editing-mode vi this-is-extra-content", EditMode::Vi),
("set editing-mode emacs this-is-extra-content", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_edit_mode_requires_lowercase_set() {
let test_cases = [
"SET editing-mode vi",
"SET editing-mode emacs",
"Set editing-mode vi",
"Set editing-mode emacs",
"sET editing-mode vi",
"sET editing-mode emacs",
];
for config in test_cases {
assert_eq!(get_readline_edit_mode(config), None);
}
}
#[test]
fn test_get_readline_editing_mode_variable_name_case_insensitive() {
let test_cases = [
("set editing-mode vi", EditMode::Vi),
("set editing-mode emacs", EditMode::Emacs),
("set EDITING-MODE emacs", EditMode::Emacs),
("set EDITING-MODE vi", EditMode::Vi),
("set Editing-Mode vi", EditMode::Vi),
("set Editing-Mode emacs", EditMode::Emacs),
("set EdItInG-MoDe vi", EditMode::Vi),
("set EdItInG-MoDe emacs", EditMode::Emacs),
("set eDiTiNg-mOdE vi", EditMode::Vi),
("set eDiTiNg-mOdE emacs", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_editing_mode_variable_value_case_insensitive() {
let test_cases = [
("set editing-mode vi", EditMode::Vi),
("set editing-mode emacs", EditMode::Emacs),
("set editing-mode VI", EditMode::Vi),
("set editing-mode EMACS", EditMode::Emacs),
("set editing-mode Vi", EditMode::Vi),
("set editing-mode vI", EditMode::Vi),
("set editing-mode eMaCs", EditMode::Emacs),
("set editing-mode EmAcS", EditMode::Emacs),
("set editing-mode emACS", EditMode::Emacs),
("set editing-mode EMacs", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_editing_mode_ignores_unrecognized_variable_names() {
let input = "set unknown-variable foo";
assert_eq!(get_readline_edit_mode(input), None);
}
#[test]
fn test_get_readline_edit_mode_multiple_lines_with_comments() {
let contents = "
# This is a comment
set some-other-setting 123
# Another comment
set editing-mode vi
# One more comment
set another-setting true
";
assert_eq!(get_readline_edit_mode(contents), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_no_mode_directive() {
let config = "set blink-matching-paren on\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_multiple_lines() {
let config = "set editing-mode vi\nset blink-matching-paren on\n";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_invalid_variable_value() {
let config = "set editing-mode vim\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_characters_variable_value() {
let config = "set editing-mode vī\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_with_posix_spaces() {
let test_cases = [
("set editing-mode vi", EditMode::Vi),
("set editing-mode emacs", EditMode::Emacs),
("set editing-mode\tvi", EditMode::Vi),
("set editing-mode\temacs", EditMode::Emacs),
("set editing-mode \t \tvi", EditMode::Vi),
("set editing-mode \t \temacs", EditMode::Emacs),
("set editing-mode\t\t\t\t\tvi", EditMode::Vi),
("set editing-mode\t\t\t\t\temacs", EditMode::Emacs),
];
for (config, expected_mode) in test_cases {
assert_eq!(get_readline_edit_mode(config), Some(expected_mode));
}
}
#[test]
fn test_get_readline_edit_mode_vi_mode_with_multibyte_utf8() {
let config = "set editing-mode vī\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_emacs_mode_with_multibyte_utf8() {
let config = "set editing-mode eĦmacs\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_vi_mode_with_trailing_invalid_utf8() {
let config = b"set editing-mode vi \x80\n";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_vi_mode() {
let config = b"set editing-mode v\xFFi\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_emacs_mode() {
let config = b"set editing-mode e\xEFmacs\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_vi_mode_with_trailing_content() {
let config = b"set editing-mode vi \xFF\xFF\xFF\n";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_emacs_mode_with_trailing_content() {
let config = b"set editing-mode emacs this-\x80is-extra-content\n";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Emacs));
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_multiple_lines() {
let config = b"set editing-mode vi\nset blink-matching-paren \xC0\x80on\n";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_emacs_mode_excess_whitespace() {
let config = b"set editing-mode \x80emacs \t \n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_vi_mode_excess_whitespace() {
let config = b"set editing-mode \x80vi \t \r\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_no_mode_directive() {
let config = b"set blink-matching-\x80paren on\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_invalid_directive() {
let config = b"set editing-\x80mode vim\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_emacs_mode_with_posix_spaces() {
let config = b"set editing-mode e\xEFmacs\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_vi_mode_with_posix_spaces() {
let config = b"set editing-\x80mode\tvi\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_emacs_mode_with_multiple_posix_spaces() {
let config = b"set editing-mode \t \nem\xF4cs\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_invalid_utf8_bytes_vi_mode_with_multiple_posix_spaces() {
let config = b"set editing-\xF4mode \t \nvi\n";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_quotes() {
let test_cases = [
"set editing-mode 'emacs'",
"set editing-mode 'vi'",
"set editing-mode \"emacs\"",
"set editing-mode \"vi\"",
"set editing-mode 'emacs\"",
"set editing-mode 'vi\"",
];
for config in test_cases {
assert_eq!(get_readline_edit_mode(config), None);
}
}
#[test]
fn test_get_readline_edit_mode_last_set_directive_vi() {
let contents = "
set editing-mode emacs
set editing-mode vi
";
assert_eq!(get_readline_edit_mode(contents), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_last_set_directive_emacs() {
let contents = "
set editing-mode vi
set editing-mode emacs
";
assert_eq!(get_readline_edit_mode(contents), Some(EditMode::Emacs));
}
#[test]
fn test_get_readline_edit_mode_last_set_directive_vi_with_whitespace() {
let contents = "
set editing-mode emacs
set editing-mode vi
";
assert_eq!(get_readline_edit_mode(contents), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_last_set_directive_emacs_with_whitespace() {
let contents = "
set editing-mode vi
set editing-mode emacs
";
assert_eq!(get_readline_edit_mode(contents), Some(EditMode::Emacs));
}
#[test]
fn test_get_readline_edit_mode_multiple_set_directives_mixed() {
let contents = "
set some-other-setting 123
set editing-mode vi
set another-setting true
set editing-mode emacs
set extra-setting abc
set extra-setting xyz
set editing-mode vi
";
assert_eq!(get_readline_edit_mode(contents), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_integration_1() {
let config = "
set blink-matching-paren on
set keymap vi-command
set editing-mode emacs
set completion-ignore-case on
";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Emacs));
}
#[test]
fn test_get_readline_edit_mode_integration_2() {
let config = "
set blink-matching-paren on
set editing-mode vi
set completion-ignore-case on
set keymap vi-command
";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Vi));
}
#[test]
fn test_get_readline_edit_mode_integration_3() {
let config = "
set blink-matching-paren on
set completion-ignore-case on
set editing-mode emacs
set keymap vi-command
";
assert_eq!(get_readline_edit_mode(config), Some(EditMode::Emacs));
}
#[test]
fn test_get_readline_edit_mode_integration_4() {
let config = "
set blink-matching-paren on
set keymap vi-command
set completion-ignore-case on
";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_integration_5() {
let config = "
set blink-matching-paren on
set completion-ignore-case on
set keymap vi-command
";
assert_eq!(get_readline_edit_mode(config), None);
}
#[test]
fn test_get_readline_edit_mode_integration_6() {
let config = "
set blink-matching-paren on
set keymap vi-command
";
assert_eq!(get_readline_edit_mode(config), None);
}
}