#![allow(non_snake_case, non_upper_case_globals)]
use std::cell::RefCell;
use std::rc::Rc;
use crate::ported::eval::typval::{
tv_dict_add_tv, tv_dict_alloc, tv_dict_find, tv_get_string, tv_list_alloc,
tv_list_append_string,
};
use crate::ported::eval::typval_defs_h::{
dict_T, list_T, partial_T, typval_T, typval_vval_union::*, varnumber_T, BoolVarValue,
BoolVarValue::*, SpecialVarValue, SpecialVarValue::*, VarLockStatus, VarType, VarType::*,
VARNUMBER_MAX, VARNUMBER_MIN, VAR_TYPE_BLOB, VAR_TYPE_BOOL, VAR_TYPE_DICT, VAR_TYPE_FLOAT,
VAR_TYPE_FUNC, VAR_TYPE_LIST, VAR_TYPE_NUMBER, VAR_TYPE_STRING,
};
#[derive(Default)]
pub struct FuncScope {
pub fc_l_vars: dict_T,
pub fc_l_avars: dict_T,
pub fc_name: String,
}
thread_local! {
pub static globvardict: RefCell<dict_T> = RefCell::new(dict_T::default());
pub static funccal_stack: RefCell<Vec<FuncScope>> = const { RefCell::new(Vec::new()) };
pub static script_vars: RefCell<dict_T> = RefCell::new(dict_T::default());
pub static buffer_vars: RefCell<dict_T> = RefCell::new(dict_T::default());
pub static window_vars: RefCell<dict_T> = RefCell::new(dict_T::default());
pub static tabpage_vars: RefCell<dict_T> = RefCell::new(dict_T::default());
}
pub fn set_var(name: &str, name_len: usize, tv: typval_T, _copy: bool) {
let name_len = if name_len == 0 { name.len() } else { name_len };
let (scope, varname) = match find_var_ht_dict(name, name_len) {
Some((s, v)) if !v.is_empty() => (s, v),
_ => {
crate::ported::message::semsg(&format!("E461: Illegal variable name: {name}"));
return;
}
};
match scope {
VarScopeDict::Global => {
globvardict.with(|d| tv_dict_add_tv(&mut d.borrow_mut(), &varname, tv));
}
VarScopeDict::Script => {
script_vars.with(|d| tv_dict_add_tv(&mut d.borrow_mut(), &varname, tv));
}
VarScopeDict::Buffer => {
buffer_vars.with(|d| tv_dict_add_tv(&mut d.borrow_mut(), &varname, tv));
}
VarScopeDict::Window => {
window_vars.with(|d| tv_dict_add_tv(&mut d.borrow_mut(), &varname, tv));
}
VarScopeDict::Tabpage => {
tabpage_vars.with(|d| tv_dict_add_tv(&mut d.borrow_mut(), &varname, tv));
}
VarScopeDict::FuncLocal => {
funccal_stack.with(|s| {
if let Some(top) = s.borrow_mut().last_mut() {
tv_dict_add_tv(&mut top.fc_l_vars, &varname, tv);
}
});
}
VarScopeDict::VimVar => {
if let Some(idx) = VIMVARS_DEF.iter().position(|&(n, _, _)| n == varname) {
if VIMVARS_DEF[idx].2 & (VV_RO | VV_RO_SBX) == 0 {
set_vim_var_tv(idx, tv);
}
} else {
crate::ported::message::semsg(&format!("E461: Illegal variable name: {name}"));
}
}
VarScopeDict::FuncArgs => {
crate::ported::message::semsg(&format!("E461: Illegal variable name: {name}"));
}
}
}
pub fn eval_variable(name: &str) -> Option<typval_T> {
find_var(name, false)
}
const VV_COMPAT: u8 = 1;
const VV_RO: u8 = 2;
const VV_RO_SBX: u8 = 4;
pub type VimVarIndex = usize;
pub mod vv {
use super::VimVarIndex;
macro_rules! vv_indices {
($($name:ident),+ $(,)?) => { vv_indices!(@n 0usize; $($name),+); };
(@n $n:expr; $name:ident $(, $rest:ident)*) => {
pub const $name: VimVarIndex = $n;
vv_indices!(@n $n + 1; $($rest),*);
};
(@n $n:expr;) => { pub const VV_LEN: VimVarIndex = $n; };
}
vv_indices!(
VV_COUNT,
VV_COUNT1,
VV_PREVCOUNT,
VV_ERRMSG,
VV_WARNINGMSG,
VV_STATUSMSG,
VV_SHELL_ERROR,
VV_THIS_SESSION,
VV_VERSION,
VV_LNUM,
VV_TERMREQUEST,
VV_TERMRESPONSE,
VV_FNAME,
VV_LANG,
VV_LC_TIME,
VV_CTYPE,
VV_CC_FROM,
VV_CC_TO,
VV_FNAME_IN,
VV_FNAME_OUT,
VV_FNAME_NEW,
VV_FNAME_DIFF,
VV_CMDARG,
VV_FOLDSTART,
VV_FOLDEND,
VV_FOLDDASHES,
VV_FOLDLEVEL,
VV_PROGNAME,
VV_SEND_SERVER,
VV_DYING,
VV_EXCEPTION,
VV_THROWPOINT,
VV_REG,
VV_CMDBANG,
VV_INSERTMODE,
VV_VAL,
VV_KEY,
VV_PROFILING,
VV_FCS_REASON,
VV_FCS_CHOICE,
VV_BEVAL_BUFNR,
VV_BEVAL_WINNR,
VV_BEVAL_WINID,
VV_BEVAL_LNUM,
VV_BEVAL_COL,
VV_BEVAL_TEXT,
VV_SCROLLSTART,
VV_SWAPNAME,
VV_SWAPCHOICE,
VV_SWAPCOMMAND,
VV_CHAR,
VV_MOUSE_WIN,
VV_MOUSE_WINID,
VV_MOUSE_LNUM,
VV_MOUSE_COL,
VV_OP,
VV_SEARCHFORWARD,
VV_HLSEARCH,
VV_OLDFILES,
VV_WINDOWID,
VV_PROGPATH,
VV_COMPLETED_ITEM,
VV_OPTION_NEW,
VV_OPTION_OLD,
VV_OPTION_OLDLOCAL,
VV_OPTION_OLDGLOBAL,
VV_OPTION_COMMAND,
VV_OPTION_TYPE,
VV_ERRORS,
VV_FALSE,
VV_TRUE,
VV_NULL,
VV_NUMBERMAX,
VV_NUMBERMIN,
VV_NUMBERSIZE,
VV_VIM_DID_ENTER,
VV_TESTING,
VV_TYPE_NUMBER,
VV_TYPE_STRING,
VV_TYPE_FUNC,
VV_TYPE_LIST,
VV_TYPE_DICT,
VV_TYPE_FLOAT,
VV_TYPE_BOOL,
VV_TYPE_BLOB,
VV_EVENT,
VV_VERSIONLONG,
VV_ECHOSPACE,
VV_ARGF,
VV_ARGV,
VV_COLLATE,
VV_EXITING,
VV_MAXCOL,
VV_STACKTRACE,
VV_VIM_DID_INIT,
VV_STDERR,
VV_MSGPACK_TYPES,
VV_NULL_STRING,
VV_NULL_LIST,
VV_NULL_DICT,
VV_NULL_BLOB,
VV_LUA,
VV_RELNUM,
VV_VIRTNUM,
VV_STARTTIME,
VV_EXITREASON,
VV_USERACTIVE,
VV_STARTREASON,
);
}
use vv::*;
pub struct VimVar {
pub vv_name: &'static str,
pub vv_tv: typval_T,
pub vv_flags: u8,
}
const VIMVARS_DEF: [(&str, VarType, u8); VV_LEN] = [
("count", VAR_NUMBER, VV_RO),
("count1", VAR_NUMBER, VV_RO),
("prevcount", VAR_NUMBER, VV_RO),
("errmsg", VAR_STRING, 0),
("warningmsg", VAR_STRING, 0),
("statusmsg", VAR_STRING, 0),
("shell_error", VAR_NUMBER, VV_RO),
("this_session", VAR_STRING, 0),
("version", VAR_NUMBER, VV_COMPAT + VV_RO),
("lnum", VAR_NUMBER, VV_RO_SBX),
("termrequest", VAR_STRING, VV_RO),
("termresponse", VAR_STRING, VV_RO),
("fname", VAR_STRING, VV_RO),
("lang", VAR_STRING, VV_RO),
("lc_time", VAR_STRING, VV_RO),
("ctype", VAR_STRING, VV_RO),
("charconvert_from", VAR_STRING, VV_RO),
("charconvert_to", VAR_STRING, VV_RO),
("fname_in", VAR_STRING, VV_RO),
("fname_out", VAR_STRING, VV_RO),
("fname_new", VAR_STRING, VV_RO),
("fname_diff", VAR_STRING, VV_RO),
("cmdarg", VAR_STRING, VV_RO),
("foldstart", VAR_NUMBER, VV_RO_SBX),
("foldend", VAR_NUMBER, VV_RO_SBX),
("folddashes", VAR_STRING, VV_RO_SBX),
("foldlevel", VAR_NUMBER, VV_RO_SBX),
("progname", VAR_STRING, VV_RO),
("servername", VAR_STRING, VV_RO),
("dying", VAR_NUMBER, VV_RO),
("exception", VAR_STRING, VV_RO),
("throwpoint", VAR_STRING, VV_RO),
("register", VAR_STRING, VV_RO),
("cmdbang", VAR_NUMBER, VV_RO),
("insertmode", VAR_STRING, VV_RO),
("val", VAR_UNKNOWN, VV_RO),
("key", VAR_UNKNOWN, VV_RO),
("profiling", VAR_NUMBER, VV_RO),
("fcs_reason", VAR_STRING, VV_RO),
("fcs_choice", VAR_STRING, 0),
("beval_bufnr", VAR_NUMBER, VV_RO),
("beval_winnr", VAR_NUMBER, VV_RO),
("beval_winid", VAR_NUMBER, VV_RO),
("beval_lnum", VAR_NUMBER, VV_RO),
("beval_col", VAR_NUMBER, VV_RO),
("beval_text", VAR_STRING, VV_RO),
("scrollstart", VAR_STRING, 0),
("swapname", VAR_STRING, VV_RO),
("swapchoice", VAR_STRING, 0),
("swapcommand", VAR_STRING, VV_RO),
("char", VAR_STRING, 0),
("mouse_win", VAR_NUMBER, 0),
("mouse_winid", VAR_NUMBER, 0),
("mouse_lnum", VAR_NUMBER, 0),
("mouse_col", VAR_NUMBER, 0),
("operator", VAR_STRING, VV_RO),
("searchforward", VAR_NUMBER, 0),
("hlsearch", VAR_NUMBER, 0),
("oldfiles", VAR_LIST, 0),
("windowid", VAR_NUMBER, VV_RO_SBX),
("progpath", VAR_STRING, VV_RO),
("completed_item", VAR_DICT, 0),
("option_new", VAR_STRING, VV_RO),
("option_old", VAR_STRING, VV_RO),
("option_oldlocal", VAR_STRING, VV_RO),
("option_oldglobal", VAR_STRING, VV_RO),
("option_command", VAR_STRING, VV_RO),
("option_type", VAR_STRING, VV_RO),
("errors", VAR_LIST, 0),
("false", VAR_BOOL, VV_RO),
("true", VAR_BOOL, VV_RO),
("null", VAR_SPECIAL, VV_RO),
("numbermax", VAR_NUMBER, VV_RO),
("numbermin", VAR_NUMBER, VV_RO),
("numbersize", VAR_NUMBER, VV_RO),
("vim_did_enter", VAR_NUMBER, VV_RO),
("testing", VAR_NUMBER, 0),
("t_number", VAR_NUMBER, VV_RO),
("t_string", VAR_NUMBER, VV_RO),
("t_func", VAR_NUMBER, VV_RO),
("t_list", VAR_NUMBER, VV_RO),
("t_dict", VAR_NUMBER, VV_RO),
("t_float", VAR_NUMBER, VV_RO),
("t_bool", VAR_NUMBER, VV_RO),
("t_blob", VAR_NUMBER, VV_RO),
("event", VAR_DICT, VV_RO),
("versionlong", VAR_NUMBER, VV_RO),
("echospace", VAR_NUMBER, VV_RO),
("argf", VAR_LIST, VV_RO),
("argv", VAR_LIST, VV_RO),
("collate", VAR_STRING, VV_RO),
("exiting", VAR_NUMBER, VV_RO),
("maxcol", VAR_NUMBER, VV_RO),
("stacktrace", VAR_LIST, VV_RO),
("vim_did_init", VAR_NUMBER, VV_RO),
("stderr", VAR_NUMBER, VV_RO),
("msgpack_types", VAR_DICT, VV_RO),
("_null_string", VAR_STRING, VV_RO),
("_null_list", VAR_LIST, VV_RO),
("_null_dict", VAR_DICT, VV_RO),
("_null_blob", VAR_BLOB, VV_RO),
("lua", VAR_PARTIAL, VV_RO),
("relnum", VAR_NUMBER, VV_RO),
("virtnum", VAR_NUMBER, VV_RO),
("starttime", VAR_NUMBER, VV_RO),
("exitreason", VAR_STRING, VV_RO),
("useractive", VAR_NUMBER, VV_RO),
("startreason", VAR_STRING, VV_RO),
];
thread_local! {
pub static vimvars: RefCell<Vec<VimVar>> = RefCell::new(
VIMVARS_DEF
.iter()
.map(|&(name, t, flags)| {
let vval = match t {
VAR_NUMBER => v_number(0),
VAR_FLOAT => v_float(0.0),
VAR_STRING => v_string(String::new()),
VAR_BOOL => v_bool(BoolVarValue::kBoolVarFalse),
VAR_SPECIAL => v_special(SpecialVarValue::kSpecialVarNull),
VAR_LIST => v_list(None),
VAR_DICT => v_dict(None),
VAR_BLOB => v_blob(None),
VAR_PARTIAL => v_partial(None),
_ => v_unknown,
};
VimVar {
vv_name: name,
vv_tv: typval_T { v_type: t, v_lock: VarLockStatus::VAR_UNLOCKED, vval },
vv_flags: flags,
}
})
.collect()
);
}
pub fn evalvars_init() {
const VIM_VERSION_MAJOR: varnumber_T = 8;
const VIM_VERSION_MINOR: varnumber_T = 1;
let vim_version = VIM_VERSION_MAJOR * 100 + VIM_VERSION_MINOR;
let mpd = tv_dict_alloc();
for name in [
"nil", "boolean", "integer", "float", "string", "array", "map", "ext",
] {
let lst = tv_list_alloc(0);
let tv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(lst)),
};
tv_dict_add_tv(&mut mpd.borrow_mut(), name, tv);
}
let mut v: Vec<VimVar> = VIMVARS_DEF
.iter()
.map(|&(name, t, flags)| {
let vval = match t {
VAR_NUMBER => v_number(0),
VAR_FLOAT => v_float(0.0),
VAR_STRING => v_string(String::new()),
VAR_BOOL => v_bool(BoolVarValue::kBoolVarFalse),
VAR_SPECIAL => v_special(SpecialVarValue::kSpecialVarNull),
VAR_LIST => v_list(None),
VAR_DICT => v_dict(None),
VAR_BLOB => v_blob(None),
VAR_PARTIAL => v_partial(None),
_ => v_unknown,
};
VimVar {
vv_name: name,
vv_tv: typval_T {
v_type: t,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval,
},
vv_flags: flags,
}
})
.collect();
{
let num = |n: varnumber_T| typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(n),
};
v[VV_VERSION].vv_tv = num(vim_version);
v[VV_VERSIONLONG].vv_tv = num(vim_version * 10000);
v[VV_MSGPACK_TYPES].vv_tv = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_FIXED,
vval: v_dict(Some(mpd)),
};
v[VV_COMPLETED_ITEM].vv_tv = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_FIXED,
vval: v_dict(Some(tv_dict_alloc())),
};
v[VV_EVENT].vv_tv = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_FIXED,
vval: v_dict(Some(tv_dict_alloc())),
};
v[VV_ERRORS].vv_tv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(tv_list_alloc(0))),
};
v[VV_STDERR].vv_tv = num(2); v[VV_SEARCHFORWARD].vv_tv = num(1);
v[VV_HLSEARCH].vv_tv = num(1);
v[VV_COUNT1].vv_tv = num(1);
v[VV_STARTREASON].vv_tv = typval_T::from("normal".to_string());
v[VV_EXITING].vv_tv = typval_T {
v_type: VAR_SPECIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(SpecialVarValue::kSpecialVarNull),
};
v[VV_TYPE_NUMBER].vv_tv = num(VAR_TYPE_NUMBER);
v[VV_TYPE_STRING].vv_tv = num(VAR_TYPE_STRING);
v[VV_TYPE_FUNC].vv_tv = num(VAR_TYPE_FUNC);
v[VV_TYPE_LIST].vv_tv = num(VAR_TYPE_LIST);
v[VV_TYPE_DICT].vv_tv = num(VAR_TYPE_DICT);
v[VV_TYPE_FLOAT].vv_tv = num(VAR_TYPE_FLOAT);
v[VV_TYPE_BOOL].vv_tv = num(VAR_TYPE_BOOL);
v[VV_TYPE_BLOB].vv_tv = num(VAR_TYPE_BLOB);
v[VV_FALSE].vv_tv = typval_T {
v_type: VAR_BOOL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_bool(BoolVarValue::kBoolVarFalse),
};
v[VV_TRUE].vv_tv = typval_T {
v_type: VAR_BOOL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_bool(BoolVarValue::kBoolVarTrue),
};
v[VV_NULL].vv_tv = typval_T {
v_type: VAR_SPECIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(SpecialVarValue::kSpecialVarNull),
};
v[VV_NUMBERMAX].vv_tv = num(VARNUMBER_MAX);
v[VV_NUMBERMIN].vv_tv = num(VARNUMBER_MIN);
v[VV_NUMBERSIZE].vv_tv = num(64); v[VV_MAXCOL].vv_tv = num(i32::MAX as varnumber_T);
v[VV_LUA].vv_tv = typval_T {
v_type: VAR_PARTIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_partial(Some(Rc::new(partial_T {
pt_refcount: 1,
pt_name: String::new(),
pt_argv: Vec::new(),
pt_dict: None,
}))),
};
v[VV_REG].vv_tv = typval_T::from("\"".to_string());
}
vimvars.with(|s| *s.borrow_mut() = v);
}
pub fn get_vim_var_tv(idx: VimVarIndex) -> typval_T {
vimvars.with(|s| s.borrow()[idx].vv_tv.clone())
}
pub fn set_vim_var_tv(idx: VimVarIndex, tv: typval_T) {
vimvars.with(|s| s.borrow_mut()[idx].vv_tv = tv);
}
pub fn get_vim_var_name(idx: VimVarIndex) -> &'static str {
VIMVARS_DEF[idx].0
}
pub fn get_vim_var_nr(idx: VimVarIndex) -> varnumber_T {
match get_vim_var_tv(idx).vval {
v_number(n) => n,
_ => 0,
}
}
pub fn get_vim_var_list(idx: VimVarIndex) -> Option<Rc<RefCell<list_T>>> {
match get_vim_var_tv(idx).vval {
v_list(l) => l,
_ => None,
}
}
pub fn assert_error(msg: &str) {
let list = match get_vim_var_list(vv::VV_ERRORS) {
Some(l) => l,
None => {
let l = tv_list_alloc(1);
set_vim_var_list(vv::VV_ERRORS, Some(Rc::clone(&l)));
l
}
};
tv_list_append_string(&mut list.borrow_mut(), msg);
}
pub fn get_vim_var_dict(idx: VimVarIndex) -> Option<Rc<RefCell<dict_T>>> {
match get_vim_var_tv(idx).vval {
v_dict(d) => d,
_ => None,
}
}
pub fn get_vim_var_str(idx: VimVarIndex) -> String {
tv_get_string(&get_vim_var_tv(idx))
}
pub fn get_vim_var_partial(idx: VimVarIndex) -> Option<Rc<partial_T>> {
match get_vim_var_tv(idx).vval {
v_partial(p) => p,
_ => None,
}
}
pub fn set_vim_var_type(idx: VimVarIndex, t: VarType) {
vimvars.with(|s| s.borrow_mut()[idx].vv_tv.v_type = t);
}
pub fn set_vim_var_nr(idx: VimVarIndex, val: varnumber_T) {
set_vim_var_tv(
idx,
typval_T {
v_type: get_vim_var_tv(idx).v_type,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(val),
},
);
}
pub fn set_vim_var_bool(idx: VimVarIndex, val: BoolVarValue) {
set_vim_var_tv(
idx,
typval_T {
v_type: VAR_BOOL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_bool(val),
},
);
}
pub fn set_vim_var_special(idx: VimVarIndex, val: SpecialVarValue) {
set_vim_var_tv(
idx,
typval_T {
v_type: VAR_SPECIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(val),
},
);
}
pub fn set_vim_var_char(c: char) {
set_vim_var_string(VV_CHAR, &c.to_string());
}
pub fn set_vim_var_string(idx: VimVarIndex, val: &str) {
set_vim_var_tv(idx, typval_T::from(val.to_string()));
}
pub fn set_vim_var_list(idx: VimVarIndex, val: Option<Rc<RefCell<list_T>>>) {
set_vim_var_tv(
idx,
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(val),
},
);
}
pub fn set_vim_var_dict(idx: VimVarIndex, val: Option<Rc<RefCell<dict_T>>>) {
set_vim_var_tv(
idx,
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(val),
},
);
}
pub fn set_vim_var_partial(idx: VimVarIndex, val: Option<Rc<partial_T>>) {
vimvars.with(|s| s.borrow_mut()[idx].vv_tv.vval = v_partial(val));
}
pub fn set_internal_string_var(name: &str, value: &str) {
set_var(
name,
name.len(),
typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(value.to_string()),
},
true,
);
}
pub fn prepare_vimvar(idx: VimVarIndex) -> typval_T {
get_vim_var_tv(idx)
}
pub fn restore_vimvar(idx: VimVarIndex, save_tv: typval_T) {
set_vim_var_tv(idx, save_tv);
}
pub fn garbage_collect_globvars(_copy_id: i32) -> i32 {
0
}
pub fn garbage_collect_vimvars(_copy_id: i32) -> bool {
false
}
pub fn garbage_collect_scriptvars(_copy_id: i32) -> bool {
false
}
pub fn eval_charconvert(_from: &str, _to: &str, _fname_from: &str, _fname_to: &str) -> i32 {
crate::ported::eval_h::FAIL
}
pub fn eval_diff(_orig: &str, _new: &str, _out: &str) {}
pub fn eval_patch(_orig: &str, _diff: &str, _out: &str) {}
pub fn eval_spell_expr(_badword: &str, _expr: &str) -> Option<Rc<RefCell<list_T>>> {
None
}
pub fn list_vim_vars(_first: &mut i32) {}
pub fn list_script_vars(_first: &mut i32) {}
pub fn do_unlet(name: &str, _name_len: usize, forceit: bool) -> i32 {
let ok = crate::ported::eval_h::OK;
let fail = crate::ported::eval_h::FAIL;
let rm = |store: &'static std::thread::LocalKey<RefCell<dict_T>>, key: &str| -> i32 {
store.with(|d| {
if d.borrow_mut().dv_hashtab.shift_remove(key).is_some() || forceit {
ok
} else {
fail
}
})
};
if let Some(k) = name.strip_prefix("g:") {
return rm(&globvardict, k);
}
if let Some(k) = name.strip_prefix("s:") {
return rm(&script_vars, k);
}
if let Some(k) = name.strip_prefix("b:") {
return rm(&buffer_vars, k);
}
if let Some(k) = name.strip_prefix("w:") {
return rm(&window_vars, k);
}
if let Some(k) = name.strip_prefix("t:") {
return rm(&tabpage_vars, k);
}
let in_func = funccal_stack.with(|s| {
s.borrow_mut()
.last_mut()
.map(|top| top.fc_l_vars.dv_hashtab.shift_remove(name).is_some())
});
match in_func {
Some(true) => ok,
Some(false) => {
if forceit {
ok
} else {
fail
}
}
None => rm(&globvardict, name),
}
}
pub fn var_wrong_func_name(name: &str, _new_var: bool) -> bool {
let b = name.as_bytes();
let scoped = b.first().is_some_and(|c| b"wbst".contains(c)) && b.get(1) == Some(&b':');
let lead = if b.first().is_some_and(|&c| c != 0) && b.get(1) == Some(&b':') {
b.get(2).copied().unwrap_or(0)
} else {
b.first().copied().unwrap_or(0)
};
if !scoped && !lead.is_ascii_uppercase() && !name.contains('#') {
crate::ported::message::semsg(&format!(
"E704: Funcref variable name must start with a capital: {name}"
));
return true;
}
false
}
pub fn set_reg_var(c: u8) {
set_vim_var_string(vv::VV_REG, &(c as char).to_string());
}
pub fn evalvars_clear() {}
pub fn del_menutrans_vars() {}
pub fn check_vars(_name: &str, _len: usize) {}
pub fn set_var_const(name: &str, name_len: usize, tv: typval_T, copy: bool, _is_const: bool) {
set_var(name, name_len, tv, copy);
}
pub fn before_set_vvar(_varname: &str) -> bool {
false
}
pub fn list_glob_vars(_first: &mut i32) {}
pub fn list_arg_vars(_first: &mut i32) {}
pub fn list_hashtable_vars(_first: &mut i32) {}
pub fn list_buf_vars(_first: &mut i32) {}
pub fn list_win_vars(_first: &mut i32) {}
pub fn list_tab_vars(_first: &mut i32) {}
pub fn list_one_var_a(_prefix: &str, _name: &str, _name_len: isize) {}
pub fn list_one_var(_first: &mut i32) {}
pub fn v_exception(oldval: Option<String>) -> Option<String> {
match oldval {
None => Some(get_vim_var_str(vv::VV_EXCEPTION)),
Some(old) => {
set_vim_var_string(vv::VV_EXCEPTION, &old);
None
}
}
}
pub fn set_cmdarg(eap: Option<&exarg_T>, oldarg: Option<String>) -> Option<String> {
let oldval = get_vim_var_str(vv::VV_CMDARG);
if eap.is_none() {
set_vim_var_string(vv::VV_CMDARG, &oldarg.unwrap_or_default());
return None;
}
set_vim_var_string(vv::VV_CMDARG, "");
Some(oldval)
}
pub fn v_throwpoint(oldval: Option<String>) -> Option<String> {
match oldval {
None => Some(get_vim_var_str(vv::VV_THROWPOINT)),
Some(old) => {
set_vim_var_string(vv::VV_THROWPOINT, &old);
None
}
}
}
pub fn set_vcount(count: varnumber_T, count1: varnumber_T, set_prevcount: bool) {
if set_prevcount {
set_vim_var_nr(vv::VV_PREVCOUNT, get_vim_var_nr(vv::VV_COUNT));
}
set_vim_var_nr(vv::VV_COUNT, count);
set_vim_var_nr(vv::VV_COUNT1, count1);
}
pub fn get_var_value(name: &str) -> Option<String> {
eval_variable(name).map(|tv| tv_get_string(&tv))
}
pub fn tv_list_unlet_range(l: &mut list_T, first: usize, n1_arg: i32, has_n2: bool, n2: i32) {
let len = crate::ported::eval::typval::tv_list_len(l) as usize;
let mut last = first;
let mut n1 = n1_arg;
loop {
let next = last + 1;
n1 += 1; if next >= len || (has_n2 && n2 < n1) {
break;
}
last = next;
}
crate::ported::eval::typval::tv_list_remove_items(l, first, last);
}
pub fn get_globvar_dict() -> dict_T {
globvardict.with(|d| d.borrow().clone())
}
pub fn get_globvar_ht() -> indexmap::IndexMap<String, typval_T> {
get_globvar_dict().dv_hashtab
}
pub fn get_vimvar_dict() -> dict_T {
let mut d = dict_T::default();
for idx in 0..vv::VV_LEN {
d.dv_hashtab
.insert(get_vim_var_name(idx).to_string(), get_vim_var_tv(idx));
}
d
}
pub fn cat_prefix_varname(prefix: u8, name: &str) -> String {
format!("{}:{}", prefix as char, name)
}
pub fn skip_var_one(arg: &str) -> usize {
let b = arg.as_bytes();
if b.first() == Some(&b'@') && b.get(1).is_some_and(|&c| c != 0) {
return 2;
}
let off = usize::from(b.first() == Some(&b'$') || b.first() == Some(&b'&'));
let (end, _, _) = crate::ported::eval::find_name_end(
&arg[off..],
crate::ported::eval::FNE_INCL_BR | crate::ported::eval::FNE_CHECK_START,
);
off + end
}
pub fn skip_var_list(arg: &str, silent: bool) -> Option<(usize, i32, bool)> {
let b = arg.as_bytes();
if b.first() != Some(&b'[') {
let n = skip_var_one(arg);
return Some((n, 1, false));
}
let skipwhite = |s: &str, mut i: usize| {
let sb = s.as_bytes();
while i < sb.len() && (sb[i] == b' ' || sb[i] == b'\t') {
i += 1;
}
i
};
let mut var_count = 0;
let mut semicolon = false;
let mut p = 0usize; loop {
p = skipwhite(arg, p + 1); let s = p + skip_var_one(&arg[p..]);
if s == p {
if !silent {
crate::ported::message::semsg(&format!("E475: Invalid argument: {}", &arg[p..]));
}
return None;
}
var_count += 1;
p = skipwhite(arg, s);
match b.get(p) {
Some(&b']') => break,
Some(&b';') => {
if semicolon {
if !silent {
crate::ported::message::emsg("E452: Double ; in list of variables");
}
return None;
}
semicolon = true;
}
Some(&b',') => {}
_ => {
if !silent {
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
&arg[p..]
));
}
return None;
}
}
}
Some((p + 1, var_count, semicolon))
}
pub fn valid_varname(varname: &str) -> bool {
let bytes = varname.as_bytes();
for (i, &c) in bytes.iter().enumerate() {
if !crate::ported::eval::eval_isnamec1(c)
&& (i == 0 || !c.is_ascii_digit())
&& c != crate::ported::eval::AUTOLOAD_CHAR
{
crate::ported::message::semsg(&format!("E461: Illegal variable name: {varname}"));
return false;
}
}
true
}
pub fn eval_one_expr_in_str(p: &str, gap: &mut String, evaluate: bool) -> Option<usize> {
let b = p.as_bytes();
let mut bs = 1;
while bs < b.len() && (b[bs] == b' ' || b[bs] == b'\t') {
bs += 1;
}
if bs >= b.len() {
crate::ported::message::semsg(&format!("E1278: Missing '}}': {p}"));
return None;
}
let close = {
let mut depth = 0i32;
let mut i = bs;
let mut found = None;
while i < b.len() {
match b[i] {
b'\'' => {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
}
b'{' | b'[' | b'(' => depth += 1,
b'}' if depth == 0 => {
found = Some(i);
break;
}
b'}' | b']' | b')' => depth -= 1,
_ => {}
}
i += 1;
}
found
};
let block_end = match close {
Some(i) => i,
None => {
crate::ported::message::semsg(&format!("E1278: Missing '}}': {p}"));
return None;
}
};
if evaluate {
let expr = p[bs..block_end].trim_end();
let val = crate::ported::eval::eval_to_string(expr)?;
gap.push_str(&val);
}
Some(block_end + 1)
}
pub fn eval_all_expr_in_str(s: &str) -> Option<String> {
let b = s.as_bytes();
let mut ga = String::new();
let mut p = 0;
while p < b.len() {
let lit_start = p;
while p < b.len() && b[p] != b'{' && b[p] != b'}' {
p += 1;
}
let mut escaped_brace = false;
if p < b.len() && p + 1 < b.len() && b[p] == b[p + 1] {
p += 1;
escaped_brace = true;
} else if p < b.len() && b[p] == b'}' {
crate::ported::message::semsg(&format!("E1279: Missing '{{': {s}"));
return None;
}
ga.push_str(&s[lit_start..p]);
if p >= b.len() {
break;
}
if escaped_brace {
p += 1;
continue;
}
let consumed = eval_one_expr_in_str(&s[p..], &mut ga, true)?;
p += consumed;
}
Some(ga)
}
pub fn get_spellword(list: &list_T) -> Option<(String, i32)> {
use crate::ported::eval::typval::{tv_list_find_nr, tv_list_find_str, tv_list_len};
if tv_list_len(list) != 2 {
crate::ported::message::emsg(
"E5700: Expression from 'spellsuggest' must yield lists with exactly two values",
);
return None;
}
let word = tv_list_find_str(list, 0)?;
let score = tv_list_find_nr(list, -1, None) as i32;
Some((word, score))
}
pub fn var_set_global(name: &str, vartv: typval_T) {
crate::ported::eval::userfunc::save_funccal();
set_var(name, name.len(), vartv, false);
crate::ported::eval::userfunc::restore_funccal();
}
pub fn unref_var_dict() {}
pub fn new_script_vars() {}
pub fn init_var_dict() {}
pub enum VarScopeDict {
Global,
Script,
Buffer,
Window,
Tabpage,
FuncLocal,
FuncArgs,
VimVar,
}
pub fn find_var_ht_dict(name: &str, name_len: usize) -> Option<(VarScopeDict, String)> {
let name_len = name_len.min(name.len());
let b = name.as_bytes();
if name_len == 0 {
return None;
}
if name_len == 1 || b[1] != b':' {
if b[0] == b':' || b[0] == crate::ported::eval::AUTOLOAD_CHAR {
return None;
}
let varname = name[..name_len].to_string();
if varname == "version" {
return Some((VarScopeDict::VimVar, varname));
}
if crate::ported::eval::userfunc::get_funccal_local_dict().is_some() {
return Some((VarScopeDict::FuncLocal, varname));
}
return Some((VarScopeDict::Global, varname));
}
let varname = name[2..name_len].to_string();
if b[0] == b'g' {
return Some((VarScopeDict::Global, varname));
}
if name_len > 2
&& (varname.as_bytes().contains(&b':')
|| varname
.as_bytes()
.contains(&crate::ported::eval::AUTOLOAD_CHAR))
{
return None;
}
let scope = match b[0] {
b'b' => VarScopeDict::Buffer, b'w' => VarScopeDict::Window, b't' => VarScopeDict::Tabpage, b'v' => VarScopeDict::VimVar, b'a' => {
crate::ported::eval::userfunc::get_funccal_args_dict()?;
VarScopeDict::FuncArgs
}
b'l' => {
crate::ported::eval::userfunc::get_funccal_local_dict()?;
VarScopeDict::FuncLocal
}
b's' => VarScopeDict::Script, _ => return None,
};
Some((scope, varname))
}
pub fn find_var_ht(name: &str, name_len: usize) -> Option<(VarScopeDict, String)> {
find_var_ht_dict(name, name_len)
}
pub fn find_var(name: &str, _no_autoload: bool) -> Option<typval_T> {
let scope_snapshot = |d: &dict_T| -> typval_T {
let nd = crate::ported::eval::typval::tv_dict_alloc();
{
let mut bm = nd.borrow_mut();
for (k, v) in d.dv_hashtab.iter() {
bm.dv_hashtab.insert(k.clone(), v.clone());
}
}
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(nd)),
}
};
match name {
"v:true" => {
return Some(typval_T {
v_type: VAR_BOOL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_bool(kBoolVarTrue),
})
}
"v:false" => {
return Some(typval_T {
v_type: VAR_BOOL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_bool(kBoolVarFalse),
})
}
"v:null" => {
return Some(typval_T {
v_type: VAR_SPECIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(kSpecialVarNull),
})
}
"v:none" => {
return Some(typval_T {
v_type: VAR_SPECIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(SpecialVarValue::kSpecialVarNone),
})
}
_ => {}
}
let (scope, varname) = find_var_ht_dict(name, name.len())?;
if varname.is_empty() {
return Some(match scope {
VarScopeDict::Global => globvardict.with(|d| scope_snapshot(&d.borrow())),
VarScopeDict::Script => script_vars.with(|d| scope_snapshot(&d.borrow())),
VarScopeDict::Buffer => buffer_vars.with(|d| scope_snapshot(&d.borrow())),
VarScopeDict::Window => window_vars.with(|d| scope_snapshot(&d.borrow())),
VarScopeDict::Tabpage => tabpage_vars.with(|d| scope_snapshot(&d.borrow())),
VarScopeDict::VimVar => scope_snapshot(&get_vimvar_dict()),
VarScopeDict::FuncLocal => funccal_stack.with(|s| {
s.borrow()
.last()
.map(|f| scope_snapshot(&f.fc_l_vars))
.unwrap_or_default()
}),
VarScopeDict::FuncArgs => funccal_stack.with(|s| {
s.borrow()
.last()
.map(|f| scope_snapshot(&f.fc_l_avars))
.unwrap_or_default()
}),
});
}
match scope {
VarScopeDict::Global => globvardict.with(|d| tv_dict_find(&d.borrow(), &varname).cloned()),
VarScopeDict::Script => script_vars.with(|d| tv_dict_find(&d.borrow(), &varname).cloned()),
VarScopeDict::Buffer => buffer_vars.with(|d| tv_dict_find(&d.borrow(), &varname).cloned()),
VarScopeDict::Window => window_vars.with(|d| tv_dict_find(&d.borrow(), &varname).cloned()),
VarScopeDict::Tabpage => {
tabpage_vars.with(|d| tv_dict_find(&d.borrow(), &varname).cloned())
}
VarScopeDict::FuncLocal => funccal_stack.with(|s| {
let s = s.borrow();
tv_dict_find(&s.last()?.fc_l_vars, &varname).cloned()
}),
VarScopeDict::FuncArgs => funccal_stack.with(|s| {
let s = s.borrow();
tv_dict_find(&s.last()?.fc_l_avars, &varname).cloned()
}),
VarScopeDict::VimVar => {
let idx = VIMVARS_DEF.iter().position(|&(n, _, _)| n == varname)?;
let tv = get_vim_var_tv(idx);
if tv.v_type != VAR_UNKNOWN {
Some(tv)
} else {
None
}
}
}
}
pub fn var_exists(var: &str) -> bool {
eval_variable(var).is_some()
}
pub fn find_var_in_ht<'a>(
d: &'a dict_T,
_htname: u8,
varname: &str,
_no_autoload: bool,
) -> Option<&'a typval_T> {
if varname.is_empty() {
return None;
}
d.dv_hashtab.get(varname)
}
pub fn vars_clear(d: &mut dict_T) {
vars_clear_ext(d, true);
}
pub fn vars_clear_ext(d: &mut dict_T, _free_val: bool) {
d.dv_hashtab.clear();
}
pub fn delete_var(d: &mut dict_T, key: &str) {
d.dv_hashtab.shift_remove(key);
}
pub fn get_user_var_name(_idx: i32) -> Option<String> {
None
}
pub fn var_redir_start(_name: &str, _append: bool) -> i32 {
crate::ported::eval_h::FAIL
}
pub fn var_redir_str(_value: &str, _value_len: i32) {}
pub fn var_redir_stop() {}
pub fn reset_v_option_vars() {
for idx in [
vv::VV_OPTION_NEW,
vv::VV_OPTION_OLD,
vv::VV_OPTION_OLDLOCAL,
vv::VV_OPTION_OLDGLOBAL,
vv::VV_OPTION_COMMAND,
vv::VV_OPTION_TYPE,
] {
set_vim_var_string(idx, "");
}
}
pub fn var_check_ro(flags: i32, name: &str, _name_len: usize) -> bool {
use crate::ported::eval::typval_defs_h::{DI_FLAGS_RO, DI_FLAGS_RO_SBX};
let error_message = if flags & DI_FLAGS_RO != 0 {
Some(format!("E46: Cannot change read-only variable \"{name}\""))
} else if flags & DI_FLAGS_RO_SBX != 0 && SANDBOX {
Some(format!(
"E794: Cannot set variable in the sandbox: \"{name}\""
))
} else {
None
};
match error_message {
None => false,
Some(msg) => {
crate::ported::message::semsg(&msg);
true
}
}
}
pub fn var_check_lock(flags: i32, name: &str, _name_len: usize) -> bool {
use crate::ported::eval::typval_defs_h::DI_FLAGS_LOCK;
if flags & DI_FLAGS_LOCK == 0 {
return false;
}
crate::ported::message::semsg(&format!("E1122: Variable is locked: {name}"));
true
}
pub fn var_check_fixed(flags: i32, name: &str, _name_len: usize) -> bool {
use crate::ported::eval::typval_defs_h::DI_FLAGS_FIX;
if flags & DI_FLAGS_FIX != 0 {
crate::ported::message::semsg(&format!("E795: Cannot delete variable {name}"));
return true;
}
false
}
const SANDBOX: bool = false;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum cmdidx_T {
CMD_let,
CMD_const,
CMD_unlet,
CMD_lockvar,
CMD_unlockvar,
}
#[derive(Default)]
pub struct exarg_T {
pub arg: String,
pub cmdidx: cmdidx_T,
pub forceit: bool,
pub skip: bool,
pub nextcmd: Option<String>,
pub cmdlinep: String,
pub ea_getline: Option<Box<dyn FnMut() -> Option<String>>>,
}
impl Default for cmdidx_T {
fn default() -> Self {
cmdidx_T::CMD_let
}
}
type ex_unletlock_callback = fn(&mut crate::ported::eval::lval_T, &str, &mut exarg_T, i32) -> i32;
pub fn heredoc_get(eap: &mut exarg_T, cmd: &str, script_get: bool) -> Option<Rc<RefCell<list_T>>> {
let mut marker_indent_len = 0usize; let mut text_indent_len: i32 = 0; let mut text_indent: Option<String> = None; let mut heredoc_in_string = false; let mut line_arg = String::new();
let mut cmd_owned = cmd.to_string();
if let Some(nl) = cmd_owned.find('\n') {
heredoc_in_string = true;
line_arg = cmd_owned[nl + 1..].to_string();
cmd_owned.truncate(nl); } else if eap.ea_getline.is_none() {
crate::ported::message::emsg("E991: Cannot use =<< here");
return None;
}
let mut cmd_s = crate::ported::eval::skipwhite(&cmd_owned).to_string();
let mut evalstr = false; let mut eval_failed = false; let iswhite = |c: u8| c == b' ' || c == b'\t';
loop {
if cmd_s.starts_with("trim") && cmd_s.as_bytes().get(4).map_or(true, |&c| iswhite(c)) {
cmd_s = crate::ported::eval::skipwhite(&cmd_s[4..]).to_string();
let p = eap.cmdlinep.as_bytes();
let mut i = 0;
while i < p.len() && iswhite(p[i]) {
i += 1;
marker_indent_len += 1;
}
text_indent_len = -1; continue;
}
if cmd_s.starts_with("eval") && cmd_s.as_bytes().get(4).map_or(true, |&c| iswhite(c)) {
cmd_s = crate::ported::eval::skipwhite(&cmd_s[4..]).to_string();
evalstr = true; continue;
}
break; }
let comment_char = b'"'; let marker: String;
let cb = cmd_s.as_bytes();
if cb.first().is_some_and(|&c| c != 0 && c != comment_char) {
let m = crate::ported::eval::skipwhite(&cmd_s);
let end = m
.as_bytes()
.iter()
.position(|&c| iswhite(c) || c == 0)
.unwrap_or(m.len());
let rest = crate::ported::eval::skipwhite(&m[end..]);
if rest
.as_bytes()
.first()
.is_some_and(|&c| c != 0 && c != comment_char)
{
crate::ported::message::semsg(&format!("E488: Trailing characters: {}", &m[end..]));
return None;
}
marker = m[..end].to_string(); if !script_get
&& marker
.as_bytes()
.first()
.is_some_and(|c| c.is_ascii_lowercase())
{
crate::ported::message::emsg("E221: Marker cannot start with lower case letter");
return None;
}
} else if script_get {
marker = ".".to_string();
} else {
crate::ported::message::emsg("E172: Missing marker"); return None;
}
let l = crate::ported::eval::typval::tv_list_alloc(0); loop {
let mut mi = 0usize; let mut ti = 0i32; let theline: String;
if heredoc_in_string {
if line_arg.is_empty() {
if !script_get {
crate::ported::message::semsg(&format!("E990: Missing end marker '{marker}'"));
}
break;
}
match line_arg.find('\n') {
None => {
theline = line_arg.clone();
line_arg = String::new(); }
Some(nl) => {
theline = line_arg[..nl].to_string();
line_arg = line_arg[nl + 1..].to_string(); }
}
} else {
match eap.ea_getline.as_mut().and_then(|g| g()) {
None => {
if !script_get {
crate::ported::message::semsg(&format!(
"E990: Missing end marker '{marker}'"
));
}
break;
}
Some(t) => theline = t,
}
}
if marker_indent_len > 0
&& theline.len() >= marker_indent_len
&& eap.cmdlinep.len() >= marker_indent_len
&& theline.as_bytes()[..marker_indent_len]
== eap.cmdlinep.as_bytes()[..marker_indent_len]
{
mi = marker_indent_len;
}
if marker == theline[mi..] {
break;
}
if eval_failed {
continue;
}
if text_indent_len == -1 && !theline.is_empty() {
let tb = theline.as_bytes();
let mut i = 0;
let mut tl = 0i32;
while i < tb.len() && iswhite(tb[i]) {
i += 1;
tl += 1;
}
text_indent_len = tl;
text_indent = Some(theline[..tl as usize].to_string()); }
if let Some(ind) = &text_indent {
let tb = theline.as_bytes();
let ib = ind.as_bytes();
let mut t = 0i32;
while (t as usize) < text_indent_len as usize {
if tb.get(t as usize) != ib.get(t as usize) {
break;
}
t += 1;
}
ti = t;
}
let str_slice = &theline[ti as usize..]; if evalstr && !eap.skip {
match eval_all_expr_in_str(str_slice) {
None => {
eval_failed = true; continue;
}
Some(s) => crate::ported::eval::typval::tv_list_append_allocated_string(
&mut l.borrow_mut(),
s,
),
}
} else {
tv_list_append_string(&mut l.borrow_mut(), str_slice); }
}
if heredoc_in_string {
eap.nextcmd = Some(line_arg);
}
if eval_failed {
crate::ported::eval::typval::tv_list_free(&mut l.borrow_mut());
return None;
}
Some(l)
}
pub fn ex_let(eap: &mut exarg_T) {
use crate::ported::eval::{ends_excmd, skipwhite};
let is_const = eap.cmdidx == cmdidx_T::CMD_const; let arg = eap.arg.clone(); let mut rettv = typval_T::default(); let mut first = 1i32;
let (argend_off, var_count, semicolon) = match skip_var_list(&arg, false) {
Some(x) => x,
None => return, };
let expr_full = skipwhite(&arg[argend_off..]);
let eb = expr_full.as_bytes();
let concat = expr_full.starts_with("..="); let has_assign = eb.first() == Some(&b'=')
|| (eb.first().is_some_and(|&c| b"+-*/%.".contains(&c)) && eb.get(1) == Some(&b'='));
if !has_assign && !concat {
if arg.as_bytes().first() == Some(&b'[') {
crate::ported::message::emsg("E474: Invalid argument"); } else if !ends_excmd(arg.as_bytes().first().copied().unwrap_or(0)) {
list_arg_vars(&mut first);
} else if !eap.skip {
list_glob_vars(&mut first);
list_buf_vars(&mut first);
list_win_vars(&mut first);
list_tab_vars(&mut first);
list_script_vars(&mut first);
list_vim_vars(&mut first);
}
eap.nextcmd = None; return;
}
if eb.first() == Some(&b'=') && eb.get(1) == Some(&b'<') && eb.get(2) == Some(&b'<') {
if let Some(l) = heredoc_get(eap, &expr_full[3..], false) {
rettv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
if !eap.skip {
let arg2 = eap.arg.clone();
ex_let_vars(
&arg2,
&mut rettv,
false,
if semicolon { 1 } else { 0 },
var_count,
is_const,
Some("="),
);
}
crate::ported::eval::typval::tv_clear(&mut rettv); }
return;
}
rettv.v_type = VAR_UNKNOWN;
let mut op = String::from("=");
let expr_after: &str;
if eb.first() != Some(&b'=') {
let c = eb.first().copied().unwrap_or(0);
let mut adv = 2usize; if b"+-*/%.".contains(&c) {
op = (c as char).to_string(); if c == b'.' && eb.get(1) == Some(&b'.') {
adv = 3; }
}
expr_after = &expr_full[adv..];
} else {
expr_after = &expr_full[1..]; }
let expr_s = skipwhite(expr_after);
let mut evalarg = crate::ported::eval::evalarg_T {
eval_flags: if eap.skip {
0
} else {
crate::ported::eval::EVAL_EVALUATE
},
};
let eval_res = crate::ported::eval::eval0(expr_s, &mut rettv, Some(&mut evalarg));
if !eap.skip && eval_res != crate::ported::eval_h::FAIL {
let arg2 = eap.arg.clone();
ex_let_vars(
&arg2,
&mut rettv,
false,
if semicolon { 1 } else { 0 },
var_count,
is_const,
Some(&op),
);
}
if eval_res != crate::ported::eval_h::FAIL {
crate::ported::eval::typval::tv_clear(&mut rettv); }
}
pub fn ex_let_vars(
arg_start: &str,
tv: &mut typval_T,
copy: bool,
semicolon: i32,
var_count: i32,
is_const: bool,
op: Option<&str>,
) -> i32 {
use crate::ported::eval::skipwhite;
use crate::ported::eval_h::{FAIL, OK};
if arg_start.as_bytes().first() != Some(&b'[') {
if ex_let_one(arg_start, tv, copy, is_const, op, op).is_none() {
return FAIL;
}
return OK;
}
if tv.v_type != VAR_LIST {
crate::ported::message::emsg("E714: List required");
return FAIL;
}
let l_opt: Option<Rc<RefCell<list_T>>> = match &tv.vval {
v_list(x) => x.clone(),
_ => None,
};
let len = l_opt
.as_ref()
.map(|l| crate::ported::eval::typval::tv_list_len(&l.borrow()))
.unwrap_or(0); if semicolon == 0 && var_count < len {
crate::ported::message::emsg("E687: Less targets than List items"); return FAIL;
}
if var_count - semicolon > len {
crate::ported::message::emsg("E688: More targets than List items"); return FAIL;
}
let l = match l_opt {
Some(l) => l,
None => return FAIL,
};
let mut item_idx = 0usize;
let mut rest_len = len as usize;
let mut pos = 0usize; while arg_start.as_bytes()[pos] != b']' {
let after = skipwhite(&arg_start[pos + 1..]);
let start = arg_start.len() - after.len();
let mut item_tv = l
.borrow()
.lv_items
.get(item_idx)
.map(|it| it.li_tv.clone())
.unwrap_or_default();
let r = match ex_let_one(
&arg_start[start..],
&mut item_tv,
true,
is_const,
Some(",;]"),
op,
) {
Some(x) => start + x,
None => return FAIL, };
rest_len -= 1; item_idx += 1; let sw = skipwhite(&arg_start[r..]);
let newpos = arg_start.len() - sw.len();
match arg_start.as_bytes().get(newpos).copied() {
Some(b';') => {
let rest_list = crate::ported::eval::typval::tv_list_alloc(rest_len as isize); {
let src = l.borrow();
let mut rl = rest_list.borrow_mut();
let mut it = item_idx;
while it < src.lv_items.len() {
crate::ported::eval::typval::tv_list_append_tv(
&mut rl,
src.lv_items[it].li_tv.clone(),
);
it += 1;
}
}
let mut ltv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(Rc::clone(&rest_list))),
};
crate::ported::eval::typval::tv_list_ref(&mut rest_list.borrow_mut());
let sw2 = skipwhite(&arg_start[newpos + 1..]);
let start2 = arg_start.len() - sw2.len();
let r2 = ex_let_one(
&arg_start[start2..],
&mut ltv,
false,
is_const,
Some("]"),
op,
);
crate::ported::eval::typval::tv_clear(&mut ltv); if r2.is_none() {
return FAIL; }
break; }
Some(b',') | Some(b']') => {
pos = newpos;
}
_ => {
crate::ported::message::emsg("E473: Internal error: ex_let_vars()");
return FAIL;
}
}
}
OK }
fn ex_let_env(
arg: &str,
tv: &mut typval_T,
is_const: bool,
endchars: Option<&str>,
op: Option<&str>,
) -> Option<usize> {
use crate::ported::eval::skipwhite;
if is_const {
crate::ported::message::emsg("E996: Cannot lock an environment variable"); return None;
}
let mut arg_end = None; let after = &arg[1..];
let len = crate::ported::eval::get_env_len(after) as usize;
if len == 0 {
crate::ported::message::semsg(&format!("E475: Invalid argument: {arg}"));
} else {
let opc = op.and_then(|o| o.as_bytes().first().copied());
if op.is_some() && matches!(opc, Some(c) if b"+-*/%".contains(&c)) {
crate::ported::message::semsg(&format!(
"E734: Wrong variable type for {}=",
op.unwrap()
));
} else if endchars.is_some() && {
let nc = skipwhite(&after[len..])
.as_bytes()
.first()
.copied()
.unwrap_or(0);
!(nc == 0 || endchars.unwrap().as_bytes().contains(&nc))
} {
crate::ported::message::emsg("E18: Unexpected characters in :let"); } else {
let name = &after[..len]; let mut p = crate::ported::eval::typval::tv_get_string_chk(tv); if let (Some(pv), Some('.')) = (&p, op.and_then(|o| o.chars().next())) {
if let Ok(s) = std::env::var(name) {
p = Some(format!("{s}{pv}"));
}
}
if let Some(pv) = p {
std::env::set_var(name, &pv);
arg_end = Some(1 + len); }
}
}
arg_end
}
fn ex_let_option(
arg: &str,
tv: &mut typval_T,
is_const: bool,
endchars: Option<&str>,
op: Option<&str>,
) -> Option<usize> {
use crate::ported::eval::skipwhite;
use crate::ported::option_optval::{
find_option_var_end, get_option_value, is_tty_option, set_option_from_tv, OptValData,
OptValType,
};
if is_const {
crate::ported::message::emsg("E996: Cannot lock an option"); return None;
}
let (name, opt_idx, opt_flags) = find_option_var_end(arg);
let b = arg.as_bytes();
let prefix = if b.len() >= 3 && (b[1] == b'g' || b[1] == b'l') && b[2] == b':' {
2
} else {
0
};
let Some(name) = name else {
crate::ported::message::emsg("E18: Unexpected characters in :let"); return None;
};
let p = 1 + prefix + name.len();
let nc = skipwhite(&arg[p.min(arg.len())..])
.as_bytes()
.first()
.copied()
.unwrap_or(0);
let end_ok = match endchars {
None => true,
Some(ec) => nc == 0 || ec.as_bytes().contains(&nc),
};
if !end_ok {
crate::ported::message::emsg("E18: Unexpected characters in :let"); return None;
}
if is_tty_option(&name) {
return Some(p);
}
let curval = get_option_value(opt_idx, opt_flags);
if curval.r#type == OptValType::kOptValTypeNil {
crate::ported::message::semsg(&format!("E355: Unknown option: {name}"));
return None;
}
if let Some(o) = op {
let oc = o.as_bytes().first().copied().unwrap_or(0);
let is_string = curval.r#type == OptValType::kOptValTypeString;
if oc != b'=' && ((!is_string && oc == b'.') || (is_string && oc != b'.')) {
crate::ported::message::semsg(&format!("E734: Wrong variable type for {o}="));
return None;
}
}
let combined: typval_T = match op {
Some(o) if o.as_bytes().first().copied().unwrap_or(0) != b'=' => {
let oc = o.as_bytes()[0];
if curval.r#type == OptValType::kOptValTypeString {
let cur_s = match &curval.data {
OptValData::string(s) => s.clone(),
_ => String::new(),
};
typval_T::from(format!("{cur_s}{}", tv_get_string(tv))) } else {
let cur_n = match &curval.data {
OptValData::number(n) => *n as varnumber_T, OptValData::boolean(t) => *t as i64 as varnumber_T,
_ => 0,
};
let new_n = crate::ported::eval::typval::tv_get_number_chk(tv, None); let r = match oc {
b'+' => cur_n + new_n, b'-' => cur_n - new_n, b'*' => cur_n * new_n, b'/' => crate::ported::eval::num_divide(cur_n, new_n), b'%' => crate::ported::eval::num_modulus(cur_n, new_n), _ => new_n,
};
typval_T::from(r as varnumber_T)
}
}
_ => tv.clone(),
};
set_option_from_tv(&name, &combined);
Some(p) }
fn ex_let_register(
arg: &str,
tv: &mut typval_T,
is_const: bool,
endchars: Option<&str>,
op: Option<&str>,
) -> Option<usize> {
use crate::ported::eval::skipwhite;
use crate::ported::eval::typval::tv_get_string_chk;
use crate::ported::ops::{get_reg_contents, write_reg_contents, MotionType};
if is_const {
crate::ported::message::emsg("E996: Cannot lock a register"); return None;
}
let mut arg_end = None; let b = arg.as_bytes();
let regbyte = b.get(1).copied().unwrap_or(0);
let opc = op.and_then(|o| o.as_bytes().first().copied());
if op.is_some() && matches!(opc, Some(c) if b"+-*/%".contains(&c)) {
crate::ported::message::semsg(&format!("E734: Wrong variable type for {}=", op.unwrap()));
} else if endchars.is_some() && {
let nc = skipwhite(arg.get(2..).unwrap_or(""))
.as_bytes()
.first()
.copied()
.unwrap_or(0);
!(nc == 0 || endchars.unwrap().as_bytes().contains(&nc))
} {
crate::ported::message::emsg("E18: Unexpected characters in :let"); } else {
let mut p = tv_get_string_chk(tv); let regname = if regbyte == b'@' {
'"'
} else {
regbyte as char
};
if let (Some(pv), Some(b'.')) = (&p, opc) {
if let Some(lines) = get_reg_contents(regname) {
let s = lines.join("\n");
p = Some(format!("{s}{pv}"));
}
}
if let Some(pv) = p {
write_reg_contents(regname, &pv, MotionType::CharWise, false);
arg_end = Some(2); }
}
arg_end
}
fn ex_let_one(
arg: &str,
tv: &mut typval_T,
copy: bool,
is_const: bool,
endchars: Option<&str>,
op: Option<&str>,
) -> Option<usize> {
use crate::ported::eval::skipwhite;
let mut arg_end = None; match arg.as_bytes().first().copied() {
Some(b'$') => {
ex_let_env(arg, tv, is_const, endchars, op)
}
Some(b'&') => {
ex_let_option(arg, tv, is_const, endchars, op)
}
Some(b'@') => {
ex_let_register(arg, tv, is_const, endchars, op)
}
Some(c) if crate::ported::eval::eval_isnamec1(c) || c == b'{' => {
let mut lv = crate::ported::eval::lval_T::default();
let p = crate::ported::eval::get_lval(
arg,
Some(&*tv),
&mut lv,
false,
false,
0,
crate::ported::eval::FNE_CHECK_START,
); if let Some(p) = p {
if lv.ll_name.is_some() {
let nc = skipwhite(&arg[p..])
.as_bytes()
.first()
.copied()
.unwrap_or(0);
let end_ok = match endchars {
None => true,
Some(ec) => nc == 0 || ec.as_bytes().contains(&nc),
};
if !end_ok {
crate::ported::message::emsg("E18: Unexpected characters in :let");
} else {
crate::ported::eval::set_var_lval(&mut lv, p, tv, copy, is_const, op);
arg_end = Some(p); }
}
}
crate::ported::eval::clear_lval(&mut lv); arg_end
}
_ => {
crate::ported::message::semsg(&format!("E475: Invalid argument: {arg}")); None
}
}
}
pub fn ex_unlet(eap: &mut exarg_T) {
let argstart = eap.arg.clone();
let glv_flags = if eap.forceit {
crate::ported::eval::GLV_QUIET
} else {
0
};
ex_unletlock(eap, &argstart, 0, glv_flags, do_unlet_var);
}
pub fn ex_lockvar(eap: &mut exarg_T) {
use crate::ported::eval::skipwhite;
let mut arg = eap.arg.clone(); let mut deep = 2i32;
if eap.forceit {
deep = -1; } else if arg.as_bytes().first().is_some_and(|c| c.is_ascii_digit()) {
let digits: String = arg.chars().take_while(|c| c.is_ascii_digit()).collect();
deep = digits.parse::<i32>().unwrap_or(-1);
arg = skipwhite(&arg[digits.len()..]).to_string(); }
ex_unletlock(eap, &arg, deep, 0, do_lock_var); }
fn ex_unletlock(
eap: &mut exarg_T,
argstart: &str,
deep: i32,
glv_flags: i32,
callback: ex_unletlock_callback,
) {
use crate::ported::eval::{ends_excmd, skipwhite};
use crate::ported::eval_h::FAIL;
let mut arg_pos = 0usize; let mut error = false; loop {
let arg = &argstart[arg_pos..];
let name_end_off: usize;
if arg.as_bytes().first() == Some(&b'$') {
let mut lv = crate::ported::eval::lval_T::default();
lv.ll_name = Some(arg.to_string()); let after = &arg[1..]; let envlen = crate::ported::eval::get_env_len(after) as usize;
if envlen == 0 {
crate::ported::message::semsg(&format!("E475: Invalid argument: {arg}"));
return;
}
let name_end_slice = &after[envlen..];
if !error && !eap.skip && callback(&mut lv, name_end_slice, eap, deep) == FAIL {
error = true; }
name_end_off = arg_pos + 1 + envlen; } else {
let mut lv = crate::ported::eval::lval_T::default();
let p = crate::ported::eval::get_lval(
arg,
None,
&mut lv,
true,
eap.skip || error,
glv_flags,
crate::ported::eval::FNE_CHECK_START,
);
if lv.ll_name.is_none() {
error = true; }
let ne = p.map(|x| arg_pos + x);
let bad = match ne {
None => true,
Some(off) => {
let c = argstart.as_bytes().get(off).copied().unwrap_or(0);
!(c == b' ' || c == b'\t') && !ends_excmd(c)
}
};
if bad {
if let Some(off) = ne {
crate::ported::message::semsg(&format!(
"E488: Trailing characters: {}",
&argstart[off..]
)); }
if !(eap.skip || error) {
crate::ported::eval::clear_lval(&mut lv); }
break; }
let off = ne.unwrap();
if !error && !eap.skip && callback(&mut lv, &argstart[off..], eap, deep) == FAIL {
error = true; }
if !eap.skip {
crate::ported::eval::clear_lval(&mut lv); }
name_end_off = off;
}
let sw = skipwhite(&argstart[name_end_off..]);
arg_pos = argstart.len() - sw.len();
if ends_excmd(argstart.as_bytes().get(arg_pos).copied().unwrap_or(0)) {
break; }
}
eap.nextcmd = None; }
fn do_unlet_var(
lp: &mut crate::ported::eval::lval_T,
_name_end: &str,
eap: &mut exarg_T,
_deep: i32,
) -> i32 {
use crate::ported::eval::LlTv;
use crate::ported::eval_h::{FAIL, OK};
let forceit = eap.forceit; let mut ret = OK;
if matches!(lp.ll_tv, LlTv::Null) {
let name = lp.ll_name.clone().unwrap_or_default();
if name.as_bytes().first() == Some(&b'$') {
std::env::remove_var(&name[1..]);
} else if do_unlet(&name, lp.ll_name_len, forceit) == FAIL {
ret = FAIL; }
} else if {
let name = lp.ll_name.as_deref();
let list_locked = lp.ll_list.as_ref().is_some_and(|l| {
crate::ported::eval::typval::value_check_lock(l.borrow().lv_lock, name, lp.ll_name_len)
});
list_locked
|| lp.ll_dict.as_ref().is_some_and(|d| {
crate::ported::eval::typval::value_check_lock(
d.borrow().dv_lock,
name,
lp.ll_name_len,
)
})
} {
return FAIL; } else if lp.ll_range {
let l = lp.ll_list.clone().expect("ll_range implies ll_list");
tv_list_unlet_range(
&mut l.borrow_mut(),
lp.ll_li.unwrap_or(0),
lp.ll_n1,
!lp.ll_empty2,
lp.ll_n2,
);
} else if let Some(l) = lp.ll_list.clone() {
crate::ported::eval::typval::tv_list_item_remove(
&mut l.borrow_mut(),
lp.ll_li.unwrap_or(0),
);
} else {
if let (Some(d), Some(key)) = (lp.ll_dict.clone(), lp.ll_di.clone()) {
crate::ported::eval::typval::tv_dict_item_remove(&mut d.borrow_mut(), &key);
}
}
ret }
fn do_lock_var(
lp: &mut crate::ported::eval::lval_T,
_name_end: &str,
eap: &mut exarg_T,
deep: i32,
) -> i32 {
use crate::ported::eval::typval::tv_item_lock;
use crate::ported::eval::LlTv;
use crate::ported::eval_h::{FAIL, OK};
let lock = eap.cmdidx == cmdidx_T::CMD_lockvar; let mut ret = OK;
if matches!(lp.ll_tv, LlTv::Null) {
let name = lp.ll_name.clone().unwrap_or_default();
if name.as_bytes().first() == Some(&b'$') {
crate::ported::message::semsg(&format!("E940: Cannot lock or unlock variable {name}"));
ret = FAIL;
} else {
match find_var(&name, true) {
None => ret = FAIL, Some(mut di_tv) => {
if deep != 0 {
tv_item_lock(&mut di_tv, deep, lock, false);
set_var(&name, name.len(), di_tv, false);
}
}
}
}
} else if deep == 0 {
} else if lp.ll_range {
let l = lp.ll_list.clone().expect("ll_range implies ll_list");
let mut li = lp.ll_li.unwrap_or(0);
loop {
let len = l.borrow().lv_items.len();
if !(li < len && (lp.ll_empty2 || lp.ll_n2 >= lp.ll_n1)) {
break;
}
tv_item_lock(&mut l.borrow_mut().lv_items[li].li_tv, deep, lock, false); li += 1; lp.ll_n1 += 1; }
} else if let Some(l) = lp.ll_list.clone() {
let li = lp.ll_li.unwrap_or(0);
let mut b = l.borrow_mut();
if li < b.lv_items.len() {
tv_item_lock(&mut b.lv_items[li].li_tv, deep, lock, false);
}
} else {
if let (Some(d), Some(key)) = (lp.ll_dict.clone(), lp.ll_di.clone()) {
let mut b = d.borrow_mut();
if let Some(tv) = b.dv_hashtab.get_mut(&key) {
tv_item_lock(tv, deep, lock, false);
}
}
}
ret }
fn get_var_from(
varname: Option<String>,
rettv: &mut typval_T,
deftv: Option<&typval_T>,
htname: char,
tp: Option<Rc<RefCell<crate::ported::window::tabpage_T>>>,
win: Option<Rc<RefCell<crate::ported::window::win_T>>>,
) {
let mut done = false; *rettv = typval_T::from(String::new());
if let (Some(varname), Some(tp), Some(win)) = (varname.as_deref(), tp.as_ref(), win.as_ref()) {
let is_cur_tab = crate::ported::window::curtab
.with(|c| c.borrow().as_ref().is_some_and(|ct| Rc::ptr_eq(ct, tp)));
let is_cur_win = crate::ported::window::curwin
.with(|c| c.borrow().as_ref().is_some_and(|cw| Rc::ptr_eq(cw, win)));
let need_switch_win = !(is_cur_tab && is_cur_win);
if !need_switch_win
|| crate::ported::eval::window::switch_win() == crate::ported::eval_h::OK
{
if varname.starts_with('&') && htname != 't' {
if varname.len() == 1 {
} else {
let mut vp: &str = varname;
if crate::ported::eval::eval_option(&mut vp, rettv, true)
== crate::ported::eval_h::OK
{
done = true;
}
}
} else if varname.is_empty() {
if let Some(tv) = eval_variable(&format!("{htname}:")) {
*rettv = tv; done = true;
}
} else {
if let Some(tv) = eval_variable(&format!("{htname}:{varname}")) {
*rettv = tv; done = true;
}
}
}
}
if !done {
if let Some(d) = deftv {
if d.v_type != VAR_UNKNOWN {
*rettv = d.clone();
}
}
}
}
pub fn getwinvar(argvars: &[typval_T], rettv: &mut typval_T, off: usize) {
let tp = if off == 1 {
crate::ported::window::find_tabpage(crate::ported::eval::typval::tv_get_number_chk(
&argvars[0],
None,
) as i32)
} else {
crate::ported::window::curtab.with(|c| c.borrow().clone()) };
let win = crate::ported::eval::window::find_win_by_nr(&argvars[off], tp.clone());
let varname = crate::ported::eval::typval::tv_get_string_chk(&argvars[off + 1]);
get_var_from(varname, rettv, argvars.get(off + 2), 'w', tp, win);
}
pub fn setwinvar(argvars: &[typval_T], off: usize) {
let tp = if off == 1 {
crate::ported::window::find_tabpage(crate::ported::eval::typval::tv_get_number_chk(
&argvars[0],
None,
) as i32)
} else {
crate::ported::window::curtab.with(|c| c.borrow().clone()) };
let win = crate::ported::eval::window::find_win_by_nr(&argvars[off], tp.clone());
let varname = crate::ported::eval::typval::tv_get_string_chk(&argvars[off + 1]);
let varp = match argvars.get(off + 2) {
Some(v) => v,
None => return,
};
let (Some(win), Some(varname)) = (win, varname) else {
return;
};
let is_cur_tab = crate::ported::window::curtab.with(|c| {
c.borrow()
.as_ref()
.is_some_and(|ct| tp.as_ref().is_some_and(|t| Rc::ptr_eq(ct, t)))
});
let is_cur_win = crate::ported::window::curwin
.with(|c| c.borrow().as_ref().is_some_and(|cw| Rc::ptr_eq(cw, &win)));
let need_switch_win = !(is_cur_tab && is_cur_win);
if !need_switch_win || crate::ported::eval::window::switch_win() == crate::ported::eval_h::OK {
if let Some(optname) = varname.strip_prefix('&') {
crate::ported::option_optval::set_option_from_tv(optname, varp);
} else {
let winvarname = format!("w:{varname}");
set_var(&winvarname, winvarname.len(), varp.clone(), true);
}
}
}
#[cfg(test)]
mod misc_helper_tests {
use super::*;
use crate::ported::eval::typval::tv_get_string;
#[test]
fn internal_string_var_and_vimvar_save_restore() {
set_internal_string_var("g:port_test", "hello");
assert_eq!(
tv_get_string(&eval_variable("g:port_test").unwrap()),
"hello"
);
set_vim_var_string(vv::VV_VAL, "original");
let saved = prepare_vimvar(vv::VV_VAL);
set_vim_var_string(vv::VV_VAL, "temporary");
assert_eq!(get_vim_var_str(vv::VV_VAL), "temporary");
restore_vimvar(vv::VV_VAL, saved);
assert_eq!(get_vim_var_str(vv::VV_VAL), "original");
set_internal_string_var("g:unlet_me", "x");
assert!(eval_variable("g:unlet_me").is_some());
assert_eq!(do_unlet("g:unlet_me", 0, false), crate::ported::eval_h::OK);
assert!(eval_variable("g:unlet_me").is_none());
assert!(var_wrong_func_name("lowercase", false));
assert!(!var_wrong_func_name("Capital", false));
assert_eq!(garbage_collect_globvars(0), 0);
assert!(!garbage_collect_vimvars(0));
}
#[test]
fn prefix_and_var_skip_helpers() {
assert_eq!(cat_prefix_varname(b'g', "foo"), "g:foo");
assert_eq!(cat_prefix_varname(b'b', "count"), "b:count");
assert_eq!(skip_var_one("foo = 1"), 3);
assert_eq!(skip_var_one("@a more"), 2);
assert_eq!(skip_var_one("$PATH = 1"), 5); assert_eq!(skip_var_one("&ignorecase"), 11); assert_eq!(skip_var_one("d.key rest"), 5);
assert_eq!(skip_var_list("x = 1", false), Some((1, 1, false)));
let (consumed, n, semi) = skip_var_list("[a, b] = pair", false).unwrap();
assert_eq!(&"[a, b] = pair"[..consumed], "[a, b]");
assert_eq!((n, semi), (2, false));
let (_, n2, semi2) = skip_var_list("[head; tail] = xs", false).unwrap();
assert_eq!((n2, semi2), (2, true));
assert_eq!(skip_var_list("[a; b; c]", true), None);
}
#[test]
fn di_flags_protection_checks() {
use crate::ported::eval::typval_defs_h::{
DI_FLAGS_FIX, DI_FLAGS_LOCK, DI_FLAGS_RO, DI_FLAGS_RO_SBX,
};
assert!(var_check_ro(DI_FLAGS_RO, "g:v", 3));
assert!(!var_check_ro(0, "g:v", 3));
assert!(!var_check_ro(DI_FLAGS_RO_SBX, "g:v", 3)); assert!(var_check_lock(DI_FLAGS_LOCK, "g:v", 3));
assert!(!var_check_lock(0, "g:v", 3));
assert!(var_check_fixed(DI_FLAGS_FIX, "g:v", 3));
assert!(!var_check_fixed(0, "g:v", 3));
}
#[test]
fn list_unlet_range_removes_inclusive() {
use crate::ported::eval::typval::tv_list_append_number;
use crate::ported::eval::typval_defs_h::{list_T, typval_vval_union::v_number};
let vals = |l: &list_T| -> Vec<i64> {
l.lv_items
.iter()
.map(|it| match it.li_tv.vval {
v_number(n) => n,
_ => -1,
})
.collect()
};
let mut l = list_T::default();
for n in [0, 1, 2, 3, 4] {
tv_list_append_number(&mut l, n);
}
tv_list_unlet_range(&mut l, 1, 1, true, 3);
assert_eq!(vals(&l), vec![0, 4]);
let mut l2 = list_T::default();
for n in [0, 1, 2, 3, 4] {
tv_list_append_number(&mut l2, n);
}
tv_list_unlet_range(&mut l2, 2, 2, false, 0);
assert_eq!(vals(&l2), vec![0, 1]);
}
#[test]
fn vars_clear_and_delete() {
use crate::ported::eval::typval::tv_dict_add_nr;
use crate::ported::eval::typval_defs_h::dict_T;
let mut d = dict_T::default();
tv_dict_add_nr(&mut d, "a", 1);
tv_dict_add_nr(&mut d, "b", 2);
tv_dict_add_nr(&mut d, "c", 3);
delete_var(&mut d, "b");
assert_eq!(d.dv_hashtab.len(), 2);
assert!(!d.dv_hashtab.contains_key("b"));
assert!(d.dv_hashtab.contains_key("a"));
vars_clear(&mut d);
assert!(d.dv_hashtab.is_empty());
}
#[test]
fn vimvar_dict_snapshot() {
let d = get_vimvar_dict();
assert!(d.dv_hashtab.contains_key("count"));
assert!(d.dv_hashtab.contains_key("errors"));
assert!(!d.dv_hashtab.contains_key("v:count"));
set_internal_string_var("g:gv_snap", "ok");
assert!(get_globvar_dict().dv_hashtab.contains_key("gv_snap"));
assert!(get_globvar_ht().contains_key("gv_snap"));
}
#[test]
fn find_var_and_var_exists() {
set_internal_string_var("g:fv_probe", "yes");
assert!(var_exists("g:fv_probe"));
assert!(find_var("g:fv_probe", false).is_some());
assert!(!var_exists("g:fv_absent_xyz"));
assert!(find_var("g:fv_absent_xyz", false).is_none());
}
#[test]
fn find_var_in_ht_lookup() {
use crate::ported::eval::typval::tv_dict_add_nr;
use crate::ported::eval::typval_defs_h::{dict_T, typval_vval_union::v_number};
let mut d = dict_T::default();
tv_dict_add_nr(&mut d, "x", 5);
assert!(matches!(
find_var_in_ht(&d, b'g', "x", false).map(|tv| &tv.vval),
Some(v_number(5))
));
assert!(find_var_in_ht(&d, b'g', "missing", false).is_none());
assert!(find_var_in_ht(&d, b'g', "", false).is_none());
}
#[test]
fn expr_interpolation_in_str() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
use crate::ported::eval::typval_defs_h::typval_T;
fn hook(e: &str) -> Option<typval_T> {
match e {
"1+2" => Some(typval_T::from(3)),
"x" => Some(typval_T::from("hi".to_string())),
_ => None,
}
}
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
assert_eq!(eval_all_expr_in_str("a={1+2}b").as_deref(), Some("a=3b"));
assert_eq!(eval_all_expr_in_str("{x}!").as_deref(), Some("hi!"));
assert_eq!(eval_all_expr_in_str("{{lit}}").as_deref(), Some("{lit}"));
assert_eq!(eval_all_expr_in_str("plain").as_deref(), Some("plain"));
assert_eq!(eval_all_expr_in_str("a}b"), None);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn get_spellword_pairs() {
use crate::ported::eval::typval::{tv_list_append_number, tv_list_append_string};
use crate::ported::eval::typval_defs_h::list_T;
let mut l = list_T::default();
tv_list_append_string(&mut l, "hello");
tv_list_append_number(&mut l, 42);
assert_eq!(get_spellword(&l), Some(("hello".to_string(), 42)));
let mut bad = list_T::default();
tv_list_append_string(&mut bad, "only");
assert_eq!(get_spellword(&bad), None);
}
#[test]
fn var_set_global_from_function_scope() {
use crate::ported::eval::typval::tv_get_string;
funccal_stack.with(|s| s.borrow_mut().push(FuncScope::default()));
var_set_global("vsg_test", typval_T::from("global".to_string()));
let depth = funccal_stack.with(|s| s.borrow().len());
assert_eq!(depth, 1);
assert_eq!(
tv_get_string(&eval_variable("g:vsg_test").unwrap()),
"global"
);
funccal_stack.with(|s| s.borrow_mut().pop());
}
#[test]
fn reset_v_option_vars_clears() {
set_vim_var_string(vv::VV_OPTION_NEW, "newval");
set_vim_var_string(vv::VV_OPTION_TYPE, "global");
reset_v_option_vars();
assert_eq!(get_vim_var_str(vv::VV_OPTION_NEW), "");
assert_eq!(get_vim_var_str(vv::VV_OPTION_TYPE), "");
}
}
#[cfg(test)]
mod let_driver_tests {
use super::*;
use crate::ported::eval::typval::tv_get_string;
use crate::ported::eval::typval_defs_h::{
typval_vval_union::{v_dict, v_list, v_number},
varnumber_T, VarLockStatus,
};
fn let_eap(arg: &str) -> exarg_T {
exarg_T {
arg: arg.to_string(),
cmdidx: cmdidx_T::CMD_let,
..Default::default()
}
}
fn as_num(name: &str) -> Option<varnumber_T> {
eval_variable(name).and_then(|tv| match tv.vval {
v_number(n) => Some(n),
_ => None,
})
}
fn install_eval_hook() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
fn hook(src: &str) -> Option<crate::ported::eval::typval_defs_h::typval_T> {
let mut tv = crate::ported::eval::typval_defs_h::typval_T::default();
let mut ev = crate::ported::eval::evalarg_T {
eval_flags: crate::ported::eval::EVAL_EVALUATE,
};
if crate::ported::eval::eval0(src, &mut tv, Some(&mut ev)) == crate::ported::eval_h::OK
{
Some(tv)
} else {
None
}
}
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
}
#[test]
fn ex_let_scalar_and_compound() {
install_eval_hook();
ex_let(&mut let_eap("g:ld_a = 5"));
assert_eq!(as_num("g:ld_a"), Some(5));
ex_let(&mut let_eap("g:ld_a += 3"));
assert_eq!(as_num("g:ld_a"), Some(8));
ex_let(&mut let_eap("g:ld_s = 'ab'"));
ex_let(&mut let_eap("g:ld_s .= 'cd'"));
assert_eq!(tv_get_string(&eval_variable("g:ld_s").unwrap()), "abcd");
}
#[test]
fn ex_let_list_unpack() {
install_eval_hook();
ex_let(&mut let_eap("[g:ld_x, g:ld_y] = [11, 22]"));
assert_eq!(as_num("g:ld_x"), Some(11));
assert_eq!(as_num("g:ld_y"), Some(22));
}
#[test]
fn ex_let_list_unpack_semicolon_rest() {
install_eval_hook();
ex_let(&mut let_eap("[g:ld_h; g:ld_t] = [1, 2, 3]"));
assert_eq!(as_num("g:ld_h"), Some(1));
let tail = eval_variable("g:ld_t").unwrap();
match tail.vval {
v_list(Some(l)) => {
let nums: Vec<varnumber_T> = l
.borrow()
.lv_items
.iter()
.map(|it| match it.li_tv.vval {
v_number(n) => n,
_ => -1,
})
.collect();
assert_eq!(nums, vec![2, 3]);
}
_ => panic!("tail is not a List"),
}
}
#[test]
fn ex_let_env_and_ex_unlet() {
install_eval_hook();
ex_let(&mut let_eap("$VIMLRS_LD_ENV = 'envval'"));
assert_eq!(std::env::var("VIMLRS_LD_ENV").as_deref(), Ok("envval"));
std::env::remove_var("VIMLRS_LD_ENV");
ex_let(&mut let_eap("g:ld_u = 7"));
assert!(eval_variable("g:ld_u").is_some());
let mut eap = exarg_T {
arg: "g:ld_u".to_string(),
cmdidx: cmdidx_T::CMD_unlet,
..Default::default()
};
ex_unlet(&mut eap);
assert!(eval_variable("g:ld_u").is_none());
}
#[test]
fn ex_unlet_dict_item() {
install_eval_hook();
ex_let(&mut let_eap("g:ld_d = {'a': 1, 'b': 2}"));
let mut eap = exarg_T {
arg: "g:ld_d.a".to_string(),
cmdidx: cmdidx_T::CMD_unlet,
..Default::default()
};
ex_unlet(&mut eap);
let d = eval_variable("g:ld_d").unwrap();
match d.vval {
v_dict(Some(dd)) => {
assert!(!dd.borrow().dv_hashtab.contains_key("a"));
assert!(dd.borrow().dv_hashtab.contains_key("b"));
}
_ => panic!("g:ld_d is not a Dict"),
}
}
#[test]
fn ex_lockvar_locks_value() {
ex_let(&mut let_eap("g:ld_lk = 5"));
let mut eap = exarg_T {
arg: "g:ld_lk".to_string(),
cmdidx: cmdidx_T::CMD_lockvar,
..Default::default()
};
ex_lockvar(&mut eap);
assert_eq!(
find_var("g:ld_lk", true).unwrap().v_lock,
VarLockStatus::VAR_LOCKED
);
let mut eap2 = exarg_T {
arg: "g:ld_lk".to_string(),
cmdidx: cmdidx_T::CMD_unlockvar,
..Default::default()
};
ex_lockvar(&mut eap2);
assert_eq!(
find_var("g:ld_lk", true).unwrap().v_lock,
VarLockStatus::VAR_UNLOCKED
);
}
#[test]
fn heredoc_get_in_string() {
ex_let(&mut let_eap("g:ld_hd =<< END\nfoo\nbar\nEND"));
let l = eval_variable("g:ld_hd").unwrap();
match l.vval {
v_list(Some(list)) => {
let items: Vec<String> = list
.borrow()
.lv_items
.iter()
.map(|it| tv_get_string(&it.li_tv))
.collect();
assert_eq!(items, vec!["foo".to_string(), "bar".to_string()]);
}
_ => panic!("g:ld_hd is not a List"),
}
}
#[test]
fn heredoc_get_via_getline() {
let mut lines = vec!["one".to_string(), "two".to_string(), "EOF".to_string()].into_iter();
let mut eap = exarg_T {
arg: String::new(),
cmdidx: cmdidx_T::CMD_let,
ea_getline: Some(Box::new(move || lines.next())),
..Default::default()
};
let l = heredoc_get(&mut eap, "EOF", false).unwrap();
let items: Vec<String> = l
.borrow()
.lv_items
.iter()
.map(|it| tv_get_string(&it.li_tv))
.collect();
assert_eq!(items, vec!["one".to_string(), "two".to_string()]);
}
#[test]
fn ex_let_option_sets_and_compounds() {
install_eval_hook();
use crate::ported::option_optval::{find_option, get_option_value, OptValData};
ex_let(&mut let_eap("&tabstop = 4"));
let ts = find_option("tabstop");
assert_eq!(get_option_value(ts, 0).data, OptValData::number(4));
ex_let(&mut let_eap("&tabstop += 3"));
assert_eq!(get_option_value(ts, 0).data, OptValData::number(7));
ex_let(&mut let_eap("&filetype = 'rust'"));
let ft = find_option("filetype");
assert_eq!(
get_option_value(ft, 0).data,
OptValData::string("rust".to_string())
);
ex_let(&mut let_eap("&filetype .= 'y'"));
assert_eq!(
get_option_value(ft, 0).data,
OptValData::string("rusty".to_string())
);
}
#[test]
fn ex_let_register_writes_and_appends() {
install_eval_hook();
ex_let(&mut let_eap("@a = 'hello'"));
assert_eq!(
crate::ported::ops::get_reg_contents('a'),
Some(vec!["hello".to_string()])
);
ex_let(&mut let_eap("@a .= 'X'"));
assert_eq!(
crate::ported::ops::get_reg_contents('a')
.unwrap()
.join("\n"),
"helloX"
);
}
#[test]
fn setwinvar_getwinvar_roundtrip() {
use crate::ported::option_optval::{find_option, get_option_value, OptValData};
use crate::ported::window::{
curtab, curwin, first_tabpage, firstwin, lastwin, tabpage_T, win_T,
};
use std::cell::RefCell;
use std::rc::Rc;
let w = Rc::new(RefCell::new(win_T {
handle: 1000,
..Default::default()
}));
let tab = Rc::new(RefCell::new(tabpage_T {
handle: 1,
tp_firstwin: Some(w.clone()),
tp_curwin: Some(w.clone()),
..Default::default()
}));
firstwin.with(|c| *c.borrow_mut() = Some(w.clone()));
lastwin.with(|c| *c.borrow_mut() = Some(w.clone()));
curwin.with(|c| *c.borrow_mut() = Some(w.clone()));
first_tabpage.with(|c| *c.borrow_mut() = Some(tab.clone()));
curtab.with(|c| *c.borrow_mut() = Some(tab.clone()));
let set_args = [
typval_T::from(0 as varnumber_T),
typval_T::from("wv_x".to_string()),
typval_T::from(42 as varnumber_T),
];
setwinvar(&set_args, 0);
let get_args = [
typval_T::from(0 as varnumber_T),
typval_T::from("wv_x".to_string()),
typval_T::from("def".to_string()),
];
let mut rettv = typval_T::default();
getwinvar(&get_args, &mut rettv, 0);
assert_eq!(tv_get_string(&rettv), "42");
let miss_args = [
typval_T::from(0 as varnumber_T),
typval_T::from("wv_absent".to_string()),
typval_T::from("fallback".to_string()),
];
let mut rettv2 = typval_T::default();
getwinvar(&miss_args, &mut rettv2, 0);
assert_eq!(tv_get_string(&rettv2), "fallback");
let opt_args = [
typval_T::from(0 as varnumber_T),
typval_T::from("&shiftwidth".to_string()),
typval_T::from(3 as varnumber_T),
];
setwinvar(&opt_args, 0);
let sw = find_option("shiftwidth");
assert_eq!(get_option_value(sw, 0).data, OptValData::number(3));
}
}
#[cfg(test)]
mod find_var_ht_dict_tests {
use super::*;
use crate::ported::eval::typval::tv_get_string;
fn scope_of(name: &str) -> Option<VarScopeDict> {
find_var_ht_dict(name, name.len()).map(|(s, _)| s)
}
#[test]
fn resolves_explicit_scope_prefixes_and_strips_name() {
for (name, want) in [
("g:foo", VarScopeDict::Global),
("s:foo", VarScopeDict::Script),
("b:foo", VarScopeDict::Buffer),
("w:foo", VarScopeDict::Window),
("t:foo", VarScopeDict::Tabpage),
("v:count", VarScopeDict::VimVar),
] {
let (scope, varname) = find_var_ht_dict(name, name.len()).unwrap();
assert!(std::mem::discriminant(&scope) == std::mem::discriminant(&want));
assert_eq!(varname, &name[2..]);
}
}
#[test]
fn implicit_scope_and_compat_version() {
funccal_stack.with(|s| s.borrow_mut().clear());
assert!(matches!(scope_of("bare"), Some(VarScopeDict::Global)));
let (scope, varname) = find_var_ht_dict("version", 7).unwrap();
assert!(matches!(scope, VarScopeDict::VimVar));
assert_eq!(varname, "version");
funccal_stack.with(|s| s.borrow_mut().push(FuncScope::default()));
assert!(matches!(scope_of("bare"), Some(VarScopeDict::FuncLocal)));
funccal_stack.with(|s| s.borrow_mut().clear());
}
#[test]
fn invalid_names_and_scopeless_func_vars_return_none() {
assert!(scope_of("").is_none());
assert!(scope_of(":x").is_none());
assert!(scope_of("#x").is_none());
assert!(scope_of("x:foo").is_none());
assert!(scope_of("b:a:b").is_none());
assert!(scope_of("b:a#b").is_none());
assert!(matches!(scope_of("g:a#b"), Some(VarScopeDict::Global)));
funccal_stack.with(|s| s.borrow_mut().clear());
assert!(scope_of("l:foo").is_none());
assert!(scope_of("a:1").is_none());
funccal_stack.with(|s| s.borrow_mut().push(FuncScope::default()));
assert!(matches!(scope_of("l:foo"), Some(VarScopeDict::FuncLocal)));
assert!(matches!(scope_of("a:1"), Some(VarScopeDict::FuncArgs)));
funccal_stack.with(|s| s.borrow_mut().clear());
}
#[test]
fn set_var_find_var_roundtrip_through_one_resolver() {
for (name, key) in [
("g:rt_probe", "g:rt_probe"),
("s:rt_probe", "s:rt_probe"),
("b:rt_probe", "b:rt_probe"),
("w:rt_probe", "w:rt_probe"),
("t:rt_probe", "t:rt_probe"),
] {
set_var(name, name.len(), typval_T::from("v".to_string()), false);
assert_eq!(tv_get_string(&find_var(key, false).unwrap()), "v");
assert_eq!(tv_get_string(&eval_variable(key).unwrap()), "v");
}
}
#[test]
fn func_local_scope_roundtrip_and_empty_key_snapshot() {
use crate::ported::eval::typval::tv_dict_add_nr;
funccal_stack.with(|s| s.borrow_mut().clear());
funccal_stack.with(|s| {
let mut frame = FuncScope::default();
tv_dict_add_nr(&mut frame.fc_l_avars, "1", 7);
s.borrow_mut().push(frame);
});
set_var("loc", 3, typval_T::from(5 as varnumber_T), false);
assert_eq!(tv_get_string(&find_var("l:loc", false).unwrap()), "5");
assert_eq!(tv_get_string(&find_var("a:1", false).unwrap()), "7");
let snap = find_var("a:", false).unwrap();
match snap.vval {
v_dict(Some(d)) => assert!(d.borrow().dv_hashtab.contains_key("1")),
_ => panic!("a: is not a Dict snapshot"),
}
funccal_stack.with(|s| s.borrow_mut().clear());
}
#[test]
fn set_cmdarg_save_and_restore() {
set_vim_var_string(vv::VV_CMDARG, "prev");
let eap = exarg_T::default();
let old = set_cmdarg(Some(&eap), None);
assert_eq!(old.as_deref(), Some("prev"));
assert_eq!(get_vim_var_str(vv::VV_CMDARG), "");
let ret = set_cmdarg(None, old);
assert!(ret.is_none());
assert_eq!(get_vim_var_str(vv::VV_CMDARG), "prev");
}
}