#![allow(non_snake_case, non_upper_case_globals)]
use std::cell::RefCell;
use std::collections::HashMap;
use crate::ported::eval::typval::{tv_get_bool, tv_get_string};
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),
("runtimepath", "rtp", Kind::String, 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((lhs, val)) = part.split_once(['=', ':']) {
let (name, op) = match lhs.strip_suffix(['+', '^', '-']) {
Some(base) => (base, lhs.as_bytes()[lhs.len() - 1]),
None => (lhs, b'='),
};
if let Some((canon, _, kind, _)) = findoption(name) {
let tv = match (kind, op) {
(Kind::String, b'+') => {
let cur = tv_get_string(&get_option_value(canon));
typval_T::from(if cur.is_empty() {
val.to_string()
} else {
format!("{cur},{val}")
})
}
(Kind::String, b'^') => {
let cur = tv_get_string(&get_option_value(canon));
typval_T::from(if cur.is_empty() {
val.to_string()
} else {
format!("{val},{cur}")
})
}
(Kind::String, b'-') => {
let cur = tv_get_string(&get_option_value(canon));
typval_T::from(
cur.split(',')
.filter(|s| *s != val)
.collect::<Vec<_>>()
.join(","),
)
}
(Kind::String, _) => typval_T::from(val.to_string()),
(_, b'=') => typval_T::from(val.trim().parse::<varnumber_T>().unwrap_or(0)),
_ => continue,
};
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
);
}
}