#![allow(non_snake_case, non_upper_case_globals)]
use std::cell::RefCell;
use std::collections::HashMap;
use crate::ported::eval::typval::tv_get_bool;
use crate::ported::eval::typval_defs_h::{typval_T, varnumber_T};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Bool,
Number,
#[allow(dead_code)]
String,
}
const OPTIONS: &[(&str, &str, Kind, varnumber_T)] = &[
("ignorecase", "ic", Kind::Bool, 0),
("smartcase", "scs", Kind::Bool, 0),
("magic", "magic", Kind::Bool, 1),
("expandtab", "et", Kind::Bool, 0),
("number", "nu", Kind::Bool, 0),
("relativenumber", "rnu", Kind::Bool, 0),
("wrap", "wrap", Kind::Bool, 1),
("hlsearch", "hls", Kind::Bool, 0),
("incsearch", "is", Kind::Bool, 0),
("autoindent", "ai", Kind::Bool, 0),
("tabstop", "ts", Kind::Number, 8),
("shiftwidth", "sw", Kind::Number, 8),
("softtabstop", "sts", Kind::Number, 0),
("textwidth", "tw", Kind::Number, 0),
("scrolloff", "so", Kind::Number, 0),
];
thread_local! {
static option_values: RefCell<HashMap<String, typval_T>> = RefCell::new(HashMap::new());
}
fn findoption(name: &str) -> Option<&'static (&'static str, &'static str, Kind, varnumber_T)> {
OPTIONS
.iter()
.find(|(n, abbr, _, _)| *n == name || *abbr == name)
}
fn set_option_value(canon: &str, tv: typval_T) {
option_values.with(|m| {
m.borrow_mut().insert(canon.to_string(), tv);
});
}
pub fn get_option_value(name: &str) -> typval_T {
let Some((canon, _, kind, default)) = findoption(name) else {
return typval_T::from(String::new());
};
option_values.with(|m| {
m.borrow()
.get(*canon)
.cloned()
.unwrap_or_else(|| match kind {
Kind::String => typval_T::from(String::new()),
_ => typval_T::from(*default),
})
})
}
thread_local! {
pub static SET_HOST_HOOK: std::cell::RefCell<Option<Box<dyn Fn(&str)>>> =
const { std::cell::RefCell::new(None) };
}
pub fn do_set(args: &str) {
SET_HOST_HOOK.with(|h| {
if let Some(f) = h.borrow().as_ref() {
f(args);
}
});
for part in args.split_whitespace() {
if let Some((name, val)) = part.split_once(['=', ':']) {
if let Some((canon, _, kind, _)) = findoption(name) {
let tv = match kind {
Kind::String => typval_T::from(val.to_string()),
_ => typval_T::from(val.trim().parse().unwrap_or(0)),
};
set_option_value(canon, tv);
}
continue;
}
if let Some(name) = part.strip_suffix('!') {
if let Some((canon, _, Kind::Bool, _)) = findoption(name) {
let cur = tv_get_bool(&get_option_value(canon)) != 0;
set_option_value(canon, typval_T::from(varnumber_T::from(!cur)));
}
continue;
}
if part.ends_with('?') {
continue; }
if let Some(name) = part.strip_prefix("no") {
if let Some((canon, _, Kind::Bool, _)) = findoption(name) {
set_option_value(canon, typval_T::from(0));
continue;
}
}
if let Some(name) = part.strip_prefix("inv") {
if let Some((canon, _, Kind::Bool, _)) = findoption(name) {
let cur = tv_get_bool(&get_option_value(canon)) != 0;
set_option_value(canon, typval_T::from(varnumber_T::from(!cur)));
continue;
}
}
if let Some((canon, _, Kind::Bool, _)) = findoption(part) {
set_option_value(canon, typval_T::from(1));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_host_hook_fires_with_raw_args() {
use std::cell::RefCell;
thread_local! { static SEEN: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) }; }
super::SET_HOST_HOOK.with(|h| {
*h.borrow_mut() = Some(Box::new(|a: &str| {
SEEN.with(|s| s.borrow_mut().push(a.to_string()))
}));
});
super::do_set("number tw=80");
SEEN.with(|s| assert_eq!(s.borrow().as_slice(), &["number tw=80".to_string()]));
assert!(super::findoption("tw").is_some());
}
#[test]
fn set_and_get_bool_and_number() {
let ic = || tv_get_bool(&get_option_value("ignorecase")) != 0;
do_set("ignorecase");
assert!(ic());
do_set("noic"); assert!(!ic());
do_set("ic!"); assert!(ic());
do_set("tabstop=4");
assert_eq!(
crate::ported::eval::typval::tv_get_number_chk(&get_option_value("ts"), None),
4
);
}
}