#![allow(non_snake_case)]
use crate::ported::buffer::{buf_T, buflist_findnr, buflist_findpat, curbuf, lastbuf};
use crate::ported::eval::buffer::find_buffer;
use crate::ported::eval::encode::{encode_tv2echo, encode_tv2string};
use crate::ported::eval::list::FILTER_MAP_EVAL_HOOK;
use crate::ported::eval::typval::tv_equal;
use crate::ported::eval::typval::{
callback_from_typval, tv_blob_get, tv_check_for_number_arg, tv_check_for_string_arg,
tv_check_str_or_nr, tv_dict_watcher_add, tv_dict_watcher_remove, tv_get_number,
tv_get_string_buf, tv_get_string_buf_chk, tv_get_string_chk, Callback, CALL_FUNC_HOOK,
};
use crate::ported::eval::typval::{
tv_blob_alloc_ret, tv_blob_len, tv_dict_add_tv, tv_dict_find, tv_dict_len, tv_get_bool,
tv_get_float, tv_get_number_chk, tv_get_string, tv_list_alloc_ret, tv_list_append_number,
tv_list_append_string, tv_list_append_tv, tv_list_copy, tv_list_extend, tv_list_find_nr,
tv_list_flatten, tv_list_len, tv_list_ref,
};
use crate::ported::eval::typval::{
tv_dict_add_list, tv_dict_add_nr, tv_dict_add_str, tv_dict_alloc, tv_dict_alloc_ret,
tv_list_alloc, tv_list_append_list,
};
use crate::ported::eval::typval_defs_h::{
blob_T, list_T, typval_T, typval_vval_union::*, varnumber_T, BoolVarValue::*,
SpecialVarValue::*, VarType::*, VAR_TYPE_BLOB, VAR_TYPE_BOOL, VAR_TYPE_DICT, VAR_TYPE_FLOAT,
VAR_TYPE_FUNC, VAR_TYPE_LIST, VAR_TYPE_NUMBER, VAR_TYPE_SPECIAL, VAR_TYPE_STRING,
};
use crate::ported::eval::vars::{
assert_error, get_vim_var_str, set_vim_var_nr,
vv::{VV_EXCEPTION, VV_REG, VV_SHELL_ERROR},
};
use crate::ported::eval_h::{FAIL, OK};
use crate::ported::ex_eval::emsg_silent;
use crate::ported::grid::{ui_comp_get_grid_at_coord, ScreenGrid};
use crate::ported::message::emsg;
use crate::ported::ops::{
format_reg_type, get_reg_contents, get_reg_type, get_yank_type, write_reg_contents,
write_reg_contents_lst, MotionType,
};
use crate::ported::option::get_option_value;
use crate::ported::os::env::os_get_pid;
use crate::ported::os::time::{os_hrtime, os_localtime_r, os_strptime};
use crate::ported::profile::{
profile_end, profile_msg, profile_signed, profile_start, profile_sub, proftime_T,
};
use crate::ported::sha256::sha256_bytes;
use crate::viml_regex::regex_match;
pub fn f_len(argvars: &[typval_T], rettv: &mut typval_T) {
let arg = &argvars[0];
rettv.vval = match (arg.v_type, &arg.vval) {
(VAR_STRING, v_string(s)) => v_number(s.len() as varnumber_T),
(VAR_NUMBER, _) | (VAR_FLOAT, _) => v_number(tv_get_string(arg).len() as varnumber_T),
(VAR_LIST, v_list(Some(l))) => v_number(tv_list_len(&l.borrow()) as varnumber_T),
(VAR_DICT, v_dict(Some(d))) => v_number(tv_dict_len(&d.borrow()) as varnumber_T),
(VAR_BLOB, v_blob(Some(b))) => v_number(tv_blob_len(&b.borrow()) as varnumber_T),
(VAR_LIST, _) | (VAR_DICT, _) | (VAR_BLOB, _) => v_number(0),
_ => {
emsg("E701: Invalid type for len()");
v_number(0)
}
};
}
pub fn f_type(argvars: &[typval_T], rettv: &mut typval_T) {
let n = match argvars[0].v_type {
VAR_NUMBER => VAR_TYPE_NUMBER,
VAR_STRING => VAR_TYPE_STRING,
VAR_PARTIAL | VAR_FUNC => VAR_TYPE_FUNC,
VAR_LIST => VAR_TYPE_LIST,
VAR_DICT => VAR_TYPE_DICT,
VAR_FLOAT => VAR_TYPE_FLOAT,
VAR_BOOL => VAR_TYPE_BOOL,
VAR_SPECIAL => VAR_TYPE_SPECIAL,
VAR_BLOB => VAR_TYPE_BLOB,
VAR_UNKNOWN => {
emsg("E685: Internal error: f_type(UNKNOWN)");
-1
}
};
rettv.vval = v_number(n);
}
pub fn f_empty(argvars: &[typval_T], rettv: &mut typval_T) {
let arg = &argvars[0];
let n = match (arg.v_type, &arg.vval) {
(VAR_STRING, v_string(s)) => s.is_empty(),
(VAR_NUMBER, v_number(x)) => *x == 0,
(VAR_FLOAT, v_float(f)) => *f == 0.0,
(VAR_LIST, v_list(l)) => l.as_ref().map_or(true, |l| l.borrow().lv_len == 0),
(VAR_DICT, v_dict(d)) => d
.as_ref()
.map_or(true, |d| d.borrow().dv_hashtab.is_empty()),
(VAR_BLOB, v_blob(b)) => b.as_ref().map_or(true, |b| b.borrow().bv_ga.is_empty()),
(VAR_BOOL, v_bool(b)) => *b == kBoolVarFalse,
(VAR_SPECIAL, _) => true,
(VAR_FUNC | VAR_PARTIAL, _) => false,
_ => true,
};
rettv.vval = v_number(n as varnumber_T);
}
pub fn f_abs(argvars: &[typval_T], rettv: &mut typval_T) {
if argvars[0].v_type == VAR_FLOAT {
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(tv_get_float(&argvars[0]).abs());
} else {
rettv.vval = v_number(tv_get_number_chk(&argvars[0], None).abs());
}
}
pub fn f_str2float(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let p = s.trim_start();
let (isneg, p) = match p.strip_prefix(['+', '-']) {
Some(rest) if p.starts_with('-') => (true, rest.trim_start()),
Some(rest) => (false, rest.trim_start()),
None => (false, p),
};
let (mut val, _) = crate::ported::eval::string2float(p);
if isneg {
val *= -1.0;
}
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(val);
}
pub fn f_float2nr(argvars: &[typval_T], rettv: &mut typval_T) {
let f = tv_get_float(&argvars[0]);
let n = if f <= -(varnumber_T::MAX as f64) + f64::EPSILON {
-varnumber_T::MAX
} else if f >= varnumber_T::MAX as f64 - f64::EPSILON {
varnumber_T::MAX
} else {
f as varnumber_T
};
rettv.vval = v_number(n);
}
pub fn f_function(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
if argvars.len() < 2 {
rettv.v_type = VAR_FUNC;
rettv.vval = v_string(name);
return;
}
let mut pt_argv: Vec<typval_T> = Vec::new();
let mut pt_dict = None;
for a in &argvars[1..] {
match (a.v_type, &a.vval) {
(VAR_LIST, v_list(Some(l))) => {
pt_argv = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
}
(VAR_DICT, v_dict(Some(d))) => pt_dict = Some(d.clone()),
_ => {}
}
}
rettv.v_type = VAR_PARTIAL;
rettv.vval = v_partial(Some(std::rc::Rc::new(
crate::ported::eval::typval_defs_h::partial_T {
pt_refcount: 1,
pt_name: name,
pt_argv,
pt_dict,
},
)));
}
pub fn f_char2nr(argvars: &[typval_T], rettv: &mut typval_T) {
let n = tv_get_string(&argvars[0])
.chars()
.next()
.map_or(0, |c| c as varnumber_T);
rettv.vval = v_number(n);
}
pub fn f_nr2char(argvars: &[typval_T], rettv: &mut typval_T) {
let n = tv_get_number_chk(&argvars[0], None);
let s = char::from_u32(n as u32)
.map(String::from)
.unwrap_or_default();
rettv.v_type = VAR_STRING;
rettv.vval = v_string(s);
}
fn repeat_list(l: &std::rc::Rc<std::cell::RefCell<list_T>>, n: varnumber_T, rettv: &mut typval_T) {
let len = (n > 0) as varnumber_T * n * tv_list_len(&l.borrow()) as varnumber_T;
let out = tv_list_alloc_ret(rettv, len as isize);
let src = l.borrow();
let mut count = n;
while count > 0 {
tv_list_extend(&mut out.borrow_mut(), &src, None);
count -= 1;
}
}
fn repeat_blob(
blob: Option<&std::rc::Rc<std::cell::RefCell<blob_T>>>,
n: varnumber_T,
rettv: &mut typval_T,
) {
let out = tv_blob_alloc_ret(rettv);
let Some(blob) = blob else { return };
if n <= 0 {
return;
}
let src = blob.borrow().bv_ga.clone();
let slen = src.len();
let len = slen * n as usize;
if len == 0 {
return;
}
let mut data = vec![0u8; len];
if src.iter().all(|&b| b == 0) {
out.borrow_mut().bv_ga = data;
return;
}
for i in 0..n as usize {
data[i * slen..(i + 1) * slen].copy_from_slice(&src);
}
out.borrow_mut().bv_ga = data;
}
fn repeat_string(str_tv: &typval_T, n: varnumber_T, rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
if n <= 0 {
rettv.vval = v_string(String::new());
return;
}
let p = tv_get_string(str_tv);
let bytes = p.as_bytes();
let slen = bytes.len();
if slen == 0 {
rettv.vval = v_string(String::new());
return;
}
let Some(len) = slen.checked_mul(n as usize) else {
rettv.vval = v_string(String::new());
return;
};
let mut r = vec![0u8; len];
r[..slen].copy_from_slice(bytes);
let mut done = slen;
while done < len {
let copy_len = done.min(len - done);
r.copy_within(0..copy_len, done);
done += copy_len;
}
rettv.vval = v_string(String::from_utf8_lossy(&r).into_owned());
}
pub fn f_repeat(argvars: &[typval_T], rettv: &mut typval_T) {
let n = tv_get_number(&argvars[1]);
match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => repeat_list(l, n, rettv),
(VAR_LIST, _) => {
tv_list_alloc_ret(rettv, 0);
}
(VAR_BLOB, v_blob(b)) => repeat_blob(b.as_ref(), n, rettv),
_ => repeat_string(&argvars[0], n, rettv),
}
}
pub fn f_split(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let keepempty = argvars
.get(2)
.is_some_and(|t| tv_get_number_chk(t, None) != 0);
let pat = argvars.get(1).map(tv_get_string).filter(|p| !p.is_empty());
let parts: Vec<String> = match pat {
Some(p) => crate::viml_regex::regex_split(
&s,
&p,
tv_get_bool(&get_option_value("ignorecase")) != 0,
keepempty,
),
None => s.split_whitespace().map(String::from).collect(),
};
let l = tv_list_alloc_ret(rettv, parts.len() as isize);
let mut lb = l.borrow_mut();
for p in &parts {
tv_list_append_string(&mut lb, p);
}
}
pub fn f_matchstr(argvars: &[typval_T], rettv: &mut typval_T) {
match find_some_match(argvars) {
Some(m) if m.list_idx.is_some() => *rettv = m.item,
Some(m) => {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(m.groups.into_iter().next().unwrap_or_default());
}
None => {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
}
}
pub fn get_list_line() -> Option<String> {
None
}
pub fn find_internal_func_lua(_name: &str) -> Option<String> {
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EvalFuncDef {
pub name: &'static str,
pub min_argc: u8,
pub max_argc: u8,
pub base_arg: u8,
}
pub fn find_internal_func(name: &str) -> Option<EvalFuncDef> {
use crate::ported::eval::funcs_argc::{BUILTIN_ARGC, BUILTIN_BASE};
let i = BUILTIN_ARGC.binary_search_by(|e| e.0.cmp(name)).ok()?;
let (n, min_argc, max_argc) = BUILTIN_ARGC[i];
let base_arg = BUILTIN_BASE
.binary_search_by(|e| e.0.cmp(name))
.map_or(0, |j| BUILTIN_BASE[j].1);
Some(EvalFuncDef {
name: n,
min_argc,
max_argc,
base_arg,
})
}
pub fn check_internal_func(name: &str, argcount: i32) -> i32 {
use crate::ported::eval::funcs_argc::{BUILTIN_ARGC, BUILTIN_BASE, MAX};
let Ok(i) = BUILTIN_ARGC.binary_search_by(|e| e.0.cmp(name)) else {
return -1;
};
let (_, min_argc, max_argc) = BUILTIN_ARGC[i];
if argcount < min_argc as i32 {
emsg(&format!("E119: Not enough arguments for function: {name}"));
return -1;
}
if max_argc != MAX && argcount > max_argc as i32 {
emsg(&format!("E118: Too many arguments for function: {name}"));
return -1;
}
BUILTIN_BASE
.binary_search_by(|e| e.0.cmp(name))
.map_or(0, |j| BUILTIN_BASE[j].1 as i32)
}
pub fn call_internal_method(
fname: &str,
argvars: &[typval_T],
basetv: &typval_T,
rettv: &mut typval_T,
) -> i32 {
use crate::ported::eval::funcs_argc::{BUILTIN_ARGC, BUILTIN_BASE, MAX};
use crate::ported::eval::typval_defs_h::{VarLockStatus, VarType::VAR_FUNC};
use crate::ported::eval::userfunc::fcerr::*;
let Ok(i) = BUILTIN_ARGC.binary_search_by(|e| e.0.cmp(fname)) else {
return FCERR_UNKNOWN;
};
let (_, min_argc, max_argc) = BUILTIN_ARGC[i];
let base = BUILTIN_BASE
.binary_search_by(|e| e.0.cmp(fname))
.map_or(0u8, |j| BUILTIN_BASE[j].1);
if base == 0 {
return FCERR_NOTMETHOD;
}
let argcount = argvars.len() as i32;
if argcount + 1 < min_argc as i32 {
return FCERR_TOOFEW;
}
if max_argc != MAX && argcount + 1 > max_argc as i32 {
return FCERR_TOOMANY;
}
let base_index = (base - 1) as usize;
if argvars.len() < base_index {
return FCERR_TOOFEW;
}
let mut argv: Vec<typval_T> = argvars[..base_index].to_vec();
argv.push(basetv.clone());
argv.extend_from_slice(&argvars[base_index..]);
let callee = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(fname.to_string()),
};
match CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(&callee, &argv))
{
Some(result) => {
*rettv = result;
FCERR_NONE
}
None => FCERR_UNKNOWN,
}
}
pub fn call_internal_func(fname: &str, argvars: &[typval_T], rettv: &mut typval_T) -> i32 {
use crate::ported::eval::funcs_argc::{BUILTIN_ARGC, MAX};
use crate::ported::eval::typval_defs_h::{VarLockStatus, VarType::VAR_FUNC};
use crate::ported::eval::userfunc::fcerr::*;
let Ok(i) = BUILTIN_ARGC.binary_search_by(|e| e.0.cmp(fname)) else {
return FCERR_UNKNOWN;
};
let (_, min_argc, max_argc) = BUILTIN_ARGC[i];
let argcount = argvars.len();
if argcount < min_argc as usize {
return FCERR_TOOFEW;
}
if max_argc != MAX && argcount > max_argc as usize {
return FCERR_TOOMANY;
}
let callee = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(fname.to_string()),
};
match CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(&callee, argvars))
{
Some(result) => {
*rettv = result;
FCERR_NONE
}
None => FCERR_UNKNOWN,
}
}
pub fn return_register(regname: u8, rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(if regname == 0 {
String::new()
} else {
(regname as char).to_string()
});
}
pub fn may_add_state_char(gap: &mut String, include: Option<&str>, c: char) {
if include.map_or(true, |inc| inc.contains(c)) {
gap.push(c);
}
}
pub fn get_function_name(_idx: i32) -> Option<String> {
None
}
pub fn get_expr_name(_idx: i32) -> Option<String> {
None
}
pub fn api_wrapper(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn lua_wrapper(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn dummy_timer_due_cb() {}
pub fn dummy_timer_close_cb() {}
pub fn tv_get_buf(
tv: &typval_T,
curtab_only: bool,
) -> Option<std::rc::Rc<std::cell::RefCell<buf_T>>> {
if tv.v_type == VAR_NUMBER {
if let v_number(n) = &tv.vval {
return buflist_findnr(*n as i32);
}
}
if tv.v_type != VAR_STRING {
return None; }
let name = match &tv.vval {
v_string(s) => s,
_ => return None,
};
if name.is_empty() {
return curbuf.with(|c| c.borrow().clone());
}
if name == "$" {
return lastbuf.with(|c| c.borrow().clone());
}
let mut buf = buflist_findnr(buflist_findpat(name, true, false, curtab_only));
if buf.is_none() {
buf = find_buffer(tv); }
buf }
pub fn tv_get_buf_from_arg(tv: &typval_T) -> Option<std::rc::Rc<std::cell::RefCell<buf_T>>> {
if !tv_check_str_or_nr(tv) {
return None; }
let buf = tv_get_buf(tv, false);
buf }
pub fn get_buf_arg(arg: &typval_T) -> Option<std::rc::Rc<std::cell::RefCell<buf_T>>> {
let buf = tv_get_buf(arg, false);
if buf.is_none() {
crate::ported::message::semsg(&format!(
"E158: Invalid buffer name: {}",
tv_get_string(arg)
));
}
buf }
pub fn switch_buffer() {}
pub fn restore_buffer() {}
pub fn change_other_buffer_prepare() {}
pub fn change_other_buffer_restore() {}
pub fn non_zero_arg(argvars: &[typval_T]) -> bool {
match (&argvars[0].v_type, &argvars[0].vval) {
(VAR_NUMBER, v_number(n)) => *n != 0,
(VAR_BOOL, v_bool(b)) => *b == kBoolVarTrue,
(VAR_STRING, v_string(s)) => !s.is_empty(),
_ => false,
}
}
struct SomeMatch {
list_idx: Option<i64>,
start: i64,
end: i64,
groups: Vec<String>,
item: typval_T,
}
fn find_some_match(argvars: &[typval_T]) -> Option<SomeMatch> {
let pat = tv_get_string(&argvars[1]);
let ic = tv_get_bool(&get_option_value("ignorecase")) != 0;
let char_to_byte = |subject: &str, ci: i64| -> i64 {
subject
.char_indices()
.nth(ci as usize)
.map_or(subject.len() as i64, |(b, _)| b as i64)
};
let has_count = argvars.len() > 3 && argvars[3].v_type != VAR_UNKNOWN;
let count = if has_count {
tv_get_number(&argvars[3])
} else {
1
};
if argvars[0].v_type == VAR_LIST {
let items: Vec<typval_T> = match &argvars[0].vval {
v_list(Some(l)) => l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect(),
_ => return None,
};
let len = items.len() as i64;
let start = match argvars.get(2).filter(|t| t.v_type != VAR_UNKNOWN) {
None => 0,
Some(t) => {
let s = tv_get_number(t);
let s = if s < 0 { s + len } else { s };
if s < 0 || s >= len {
return None;
}
s
}
};
let mut remaining = count.max(1);
for idx in start..len {
let str = encode_tv2echo(&items[idx as usize]);
if let Some((s, e, groups)) = crate::viml_regex::regex_search_nth(&pat, &str, ic, 0, 1)
{
remaining -= 1;
if remaining <= 0 {
return Some(SomeMatch {
list_idx: Some(idx),
start: char_to_byte(&str, s),
end: char_to_byte(&str, e),
groups,
item: items[idx as usize].clone(),
});
}
}
}
return None;
}
let s = tv_get_string(&argvars[0]);
let nchars = s.chars().count() as i64;
let hit = match argvars.get(2).filter(|t| t.v_type != VAR_UNKNOWN) {
None => crate::viml_regex::regex_search_nth(&pat, &s, ic, 0, count),
Some(t) => {
let st = tv_get_number(t).max(0);
if st > nchars {
return None;
}
if has_count {
crate::viml_regex::regex_search_nth(&pat, &s, ic, st as usize, count)
} else {
let suffix: String = s.chars().skip(st as usize).collect();
crate::viml_regex::regex_search_nth(&pat, &suffix, ic, 0, count)
.map(|(a, b, g)| (a + st, b + st, g))
}
}
};
hit.map(|(start, end, groups)| SomeMatch {
list_idx: None,
start: char_to_byte(&s, start),
end: char_to_byte(&s, end),
groups,
item: typval_T::default(),
})
}
pub fn f_match(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(find_some_match(argvars).map_or(-1, |m| m.list_idx.unwrap_or(m.start)));
}
pub fn f_substitute(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let pat = tv_get_string(&argvars[1]);
let sub = tv_get_string(&argvars[2]);
let flags = argvars.get(3).map(tv_get_string).unwrap_or_default();
rettv.v_type = VAR_STRING;
rettv.vval = v_string(crate::viml_regex::regex_substitute(&s, &pat, &sub, &flags));
}
pub fn f_range(argvars: &[typval_T], rettv: &mut typval_T) {
let a0 = tv_get_number_chk(&argvars[0], None);
let (start, end, stride) = match argvars.len() {
1 => (0, a0 - 1, 1),
2 => (a0, tv_get_number_chk(&argvars[1], None), 1),
_ => (
a0,
tv_get_number_chk(&argvars[1], None),
tv_get_number_chk(&argvars[2], None),
),
};
let l = tv_list_alloc_ret(rettv, 0);
let mut lb = l.borrow_mut();
if stride > 0 {
let mut i = start;
while i <= end {
tv_list_append_number(&mut lb, i);
i += stride;
}
} else if stride < 0 {
let mut i = start;
while i >= end {
tv_list_append_number(&mut lb, i);
i += stride;
}
}
}
pub fn f_add(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(1 as varnumber_T);
match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => {
tv_list_append_tv(&mut l.borrow_mut(), argvars[1].clone());
*rettv = argvars[0].clone();
}
(VAR_BLOB, v_blob(Some(b))) => {
let n = tv_get_number_chk(&argvars[1], None);
b.borrow_mut().bv_ga.push(n as u8);
*rettv = argvars[0].clone();
}
_ => emsg("E897: List or Blob required"),
}
}
pub fn f_reverse(argvars: &[typval_T], rettv: &mut typval_T) {
match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => {
l.borrow_mut().lv_items.reverse();
*rettv = argvars[0].clone();
}
(VAR_BLOB, v_blob(Some(b))) => {
b.borrow_mut().bv_ga.reverse();
*rettv = argvars[0].clone();
}
(VAR_STRING, v_string(s)) => {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(reverse_text(s));
}
_ => {}
}
}
fn reverse_text(s: &str) -> String {
use crate::ported::strings::utf_iscomposing;
let chars: Vec<char> = s.chars().collect();
let mut groups: Vec<&[char]> = Vec::new();
let mut start = 0usize;
let mut i = 0usize;
while i < chars.len() {
let next = i + 1;
if next < chars.len() && utf_iscomposing(chars[next]) {
i = next;
continue;
}
groups.push(&chars[start..=i]);
i = next;
start = i;
}
groups.iter().rev().flat_map(|g| g.iter()).collect()
}
pub fn f_get(argvars: &[typval_T], rettv: &mut typval_T) {
if let (VAR_BLOB, v_blob(b)) = (argvars[0].v_type, &argvars[0].vval) {
rettv.v_type = VAR_NUMBER;
if let Some(b) = b {
let b = b.borrow();
let len = tv_blob_len(&b);
let mut idx = tv_get_number_chk(&argvars[1], None) as i32;
if idx < 0 {
idx += len;
}
if idx >= 0 && idx < len {
rettv.vval = v_number(tv_blob_get(&b, idx) as varnumber_T);
return;
}
}
match argvars.get(2) {
Some(d) => *rettv = d.clone(),
None => rettv.vval = v_number(-1),
}
return;
}
let default = argvars.get(2).cloned();
let found = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => {
let l = l.borrow();
let len = l.lv_len as varnumber_T;
let mut i = tv_get_number_chk(&argvars[1], None);
if i < 0 {
i += len;
}
l.lv_items.get(i as usize).map(|it| it.li_tv.clone())
}
(VAR_DICT, v_dict(Some(d))) => {
tv_dict_find(&d.borrow(), &tv_get_string(&argvars[1])).cloned()
}
(VAR_LIST, _) | (VAR_DICT, _) => None,
(VAR_FUNC, _) | (VAR_PARTIAL, _) => None,
_ => {
emsg("E1531: Argument of get() must be a List, Tuple, Dictionary or Blob");
return;
}
};
match found.or(default) {
Some(v) => *rettv = v,
None => rettv.vval = v_number(0),
}
}
pub fn f_has_key(argvars: &[typval_T], rettv: &mut typval_T) {
let present = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_DICT, v_dict(Some(d))) => {
tv_dict_find(&d.borrow(), &tv_get_string(&argvars[1])).is_some()
}
_ => false,
};
rettv.vval = v_number(present as varnumber_T);
}
pub fn f_keys(argvars: &[typval_T], rettv: &mut typval_T) {
crate::ported::eval::typval::tv_dict2list(
argvars,
rettv,
crate::ported::eval::typval::DictListType::kDict2ListKeys,
);
}
pub fn f_values(argvars: &[typval_T], rettv: &mut typval_T) {
crate::ported::eval::typval::tv_dict2list(
argvars,
rettv,
crate::ported::eval::typval::DictListType::kDict2ListValues,
);
}
pub fn f_max(argvars: &[typval_T], rettv: &mut typval_T) {
max_min(argvars, rettv, true);
}
pub fn f_min(argvars: &[typval_T], rettv: &mut typval_T) {
max_min(argvars, rettv, false);
}
fn max_min(argvars: &[typval_T], rettv: &mut typval_T, domax: bool) {
let pick = |acc: varnumber_T, v: varnumber_T| if domax { acc.max(v) } else { acc.min(v) };
let n = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => {
let l = l.borrow();
let mut it = l.lv_items.iter().map(|x| tv_get_number_chk(&x.li_tv, None));
match it.next() {
Some(first) => it.fold(first, pick),
None => 0,
}
}
(VAR_DICT, v_dict(Some(d))) => {
let d = d.borrow();
let mut it = d.dv_hashtab.values().map(|v| tv_get_number_chk(v, None));
match it.next() {
Some(first) => it.fold(first, pick),
None => 0,
}
}
_ => 0,
};
rettv.vval = v_number(n);
}
pub fn f_index(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
let needle = &argvars[1];
if let (VAR_BLOB, v_blob(b)) = (argvars[0].v_type, &argvars[0].vval) {
let Some(b) = b else { return };
let b = b.borrow();
let len = tv_blob_len(&b);
let mut start = argvars.get(2).map_or(0, tv_get_number) as i32;
if start < 0 {
start = (len + start).max(0);
}
for idx in start..len {
let tv = typval_T::from(tv_blob_get(&b, idx) as varnumber_T);
if tv_equal(&tv, needle, false) {
rettv.vval = v_number(idx as varnumber_T);
return;
}
}
return;
}
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
(VAR_LIST, _) => return, _ => {
emsg("E897: List or Blob required");
return;
}
};
let lb = l.borrow();
let len = lb.lv_items.len() as isize;
let mut start: isize = 0;
if let Some(a2) = argvars.get(2) {
let mut n = tv_get_number(a2) as isize;
if n < 0 {
n += len;
}
if n < 0 || n >= len {
return;
}
start = n;
}
let ic = argvars.get(3).is_some_and(|t| tv_get_number(t) != 0);
for (i, it) in lb.lv_items.iter().enumerate().skip(start as usize) {
if tv_equal(&it.li_tv, needle, ic) {
rettv.vval = v_number(i as varnumber_T);
return;
}
}
}
pub fn f_has(argvars: &[typval_T], rettv: &mut typval_T) {
use std::io::IsTerminal;
let name = tv_get_string(&argvars[0]).to_ascii_lowercase();
let n = match name.as_str() {
"ttyin" => std::io::stdin().is_terminal(),
"ttyout" => std::io::stdout().is_terminal(),
"multi_byte_encoding" => true, "gui_running" | "vim_starting" | "syntax_items" | "wsl" => false,
_ if name.starts_with("patch") || name.starts_with("nvim-") => false,
"unix" => cfg!(unix),
"linux" => cfg!(target_os = "linux"),
"mac" | "macunix" | "osx" | "osxdarwin" => cfg!(target_os = "macos"),
"bsd" => {
cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) && !cfg!(target_os = "macos")
}
"win32" => cfg!(windows),
"win64" => cfg!(all(windows, target_pointer_width = "64")),
"sun" => cfg!(target_os = "solaris"),
"fork" => cfg!(unix),
"eval" | "float" | "vimlrs" | "lambda" | "num64" | "vimscript-1" | "vim9script"
| "multi_byte" | "reltime" | "nanotime" | "iconv" | "digraphs" | "modify_fname"
| "gettext" | "byte_offset" => true,
_ => false,
};
rettv.vval = v_number(n as varnumber_T);
}
pub fn f_exists(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
let present = if let Some(au) = name.strip_prefix('#') {
au_exists(au)
} else if let Some(func) = name.strip_prefix('*') {
crate::ported::eval::typval::FUNC_EXISTS_HOOK
.with(|h| *h.borrow())
.is_some_and(|f| f(func))
} else {
crate::ported::eval::vars::eval_variable(&name).is_some()
};
rettv.vval = v_number(present as varnumber_T);
}
pub fn f_printf(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let fmt = tv_get_string(&argvars[0]);
let bytes: Vec<char> = fmt.chars().collect();
let mut out = String::new();
let mut i = 0usize;
let mut arg = 1usize;
while i < bytes.len() {
if bytes[i] != '%' {
out.push(bytes[i]);
i += 1;
continue;
}
i += 1; let mut explicit_idx: Option<usize> = None;
{
let save = i;
let mut n = 0usize;
let mut got = false;
while i < bytes.len() && bytes[i].is_ascii_digit() {
n = n * 10 + (bytes[i] as usize - '0' as usize);
got = true;
i += 1;
}
if got && i < bytes.len() && bytes[i] == '$' {
i += 1; explicit_idx = Some(n);
} else {
i = save; }
}
let mut left = false;
let mut zero = false;
let mut plus = false;
let mut space = false;
let mut alt = false; while i < bytes.len() && matches!(bytes[i], '-' | '0' | '+' | ' ' | '#') {
match bytes[i] {
'-' => left = true,
'0' => zero = true,
'+' => plus = true,
' ' => space = true,
'#' => alt = true,
_ => {}
}
i += 1;
}
let mut width = 0usize;
if i < bytes.len() && bytes[i] == '*' {
i += 1;
let w = argvars.get(arg).map_or(0, |t| tv_get_number_chk(t, None));
arg += 1;
if w < 0 {
left = true;
width = (-w) as usize;
} else {
width = w as usize;
}
} else {
while i < bytes.len() && bytes[i].is_ascii_digit() {
width = width * 10 + (bytes[i] as usize - '0' as usize);
i += 1;
}
}
let mut prec: Option<usize> = None;
if i < bytes.len() && bytes[i] == '.' {
i += 1;
if i < bytes.len() && bytes[i] == '*' {
i += 1;
let p = argvars.get(arg).map_or(0, |t| tv_get_number_chk(t, None));
arg += 1;
prec = Some(p.max(0) as usize);
} else {
let mut p = 0usize;
while i < bytes.len() && bytes[i].is_ascii_digit() {
p = p * 10 + (bytes[i] as usize - '0' as usize);
i += 1;
}
prec = Some(p);
}
}
const PRINTF_MAX: usize = 1024 * 1024;
if width > PRINTF_MAX {
emsg(&format!("E1510: Value too large: {width}"));
rettv.vval = v_string(String::new());
return;
}
if let Some(p) = prec {
if p > PRINTF_MAX {
emsg(&format!("E1510: Value too large: {p}"));
rettv.vval = v_string(String::new());
return;
}
}
let Some(conv) = bytes.get(i).copied() else {
out.push('%');
break;
};
i += 1;
if conv == '%' {
out.push('%');
continue;
}
let cur = argvars.get(explicit_idx.unwrap_or(arg));
let mut nonfinite = false;
let mut nonfinite_nan = false;
let nf_str = |v: f64, upper: bool| -> String {
if v.is_nan() {
if upper { "NAN" } else { "nan" }.to_string()
} else if v < 0.0 {
if upper { "-INF" } else { "-inf" }.to_string()
} else if upper {
"INF".to_string()
} else {
"inf".to_string()
}
};
let core = match conv {
'd' | 'i' => cur.map_or(0, |t| tv_get_number_chk(t, None)).to_string(),
's' | 'S' => {
let mut s = cur.map(encode_tv2echo).unwrap_or_default();
if let Some(p) = prec {
if s.len() > p {
let mut end = p;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
s.truncate(end);
}
}
s
}
'f' | 'F' => {
let v = cur.map_or(0.0, tv_get_float);
if !v.is_finite() {
nonfinite = true;
nonfinite_nan = v.is_nan();
nf_str(v, conv == 'F')
} else {
format!("{:.*}", prec.unwrap_or(6), v)
}
}
'x' => format!("{:x}", cur.map_or(0, |t| tv_get_number_chk(t, None))),
'X' => format!("{:X}", cur.map_or(0, |t| tv_get_number_chk(t, None))),
'o' => format!("{:o}", cur.map_or(0, |t| tv_get_number_chk(t, None))),
'b' | 'B' => format!("{:b}", cur.map_or(0, |t| tv_get_number_chk(t, None))),
'u' => (cur.map_or(0, |t| tv_get_number_chk(t, None)) as u64).to_string(),
'c' => {
let byte = (cur.map_or(0, |t| tv_get_number_chk(t, None)) & 0xFF) as u32;
char::from_u32(byte).unwrap_or('\u{0}').to_string()
}
'g' | 'G' => {
let v = cur.map_or(0.0, tv_get_float);
if !v.is_finite() {
nonfinite = true;
nonfinite_nan = v.is_nan();
nf_str(v, conv == 'G')
} else {
let s = crate::ported::eval::encode::vim_float_g(v, prec.map(|p| p as i32));
if conv == 'G' {
s.to_uppercase()
} else {
s
}
}
}
'e' | 'E' => {
let v = cur.map_or(0.0, tv_get_float);
if !v.is_finite() {
nonfinite = true;
nonfinite_nan = v.is_nan();
nf_str(v, conv == 'E')
} else {
let s = format!("{:.*e}", prec.unwrap_or(6), v);
if let Some(ep) = s.find('e') {
let (m, ex) = s.split_at(ep);
let en: i32 = ex[1..].parse().unwrap_or(0);
format!(
"{m}{}{}{:02}",
conv,
if en < 0 { '-' } else { '+' },
en.abs()
)
} else {
s
}
}
}
other => {
out.push('%');
out.push(other);
continue;
}
};
if explicit_idx.is_none() {
arg += 1;
}
let mut zero = zero;
let mut core = core;
if nonfinite {
zero = false;
}
if matches!(conv, 'd' | 'i' | 'o' | 'u' | 'x' | 'X' | 'b' | 'B') {
if let Some(p) = prec {
zero = false;
let (neg, digits) = match core.strip_prefix('-') {
Some(rest) => (true, rest.to_string()),
None => (false, core),
};
let digits = if p == 0 && digits == "0" {
String::new()
} else if digits.len() < p {
format!("{}{digits}", "0".repeat(p - digits.len()))
} else {
digits
};
core = if neg { format!("-{digits}") } else { digits };
}
}
let signed = matches!(conv, 'd' | 'i' | 'f' | 'F' | 'e' | 'E' | 'g' | 'G');
let (sign, core) = if nonfinite_nan {
("", core)
} else if signed {
if let Some(rest) = core.strip_prefix('-') {
("-", rest.to_string())
} else if plus {
("+", core)
} else if space {
(" ", core)
} else {
("", core)
}
} else if alt {
let prefix = match conv {
'x' if core != "0" => "0x",
'X' if core != "0" => "0X",
'b' if core != "0" => "0b",
'B' if core != "0" => "0B",
'o' if !core.starts_with('0') => "0",
_ => "",
};
(prefix, core)
} else {
("", core)
};
let visible = if conv == 'S' {
core.chars().count()
} else {
core.len()
};
let len = sign.len() + visible;
if len >= width {
out.push_str(sign);
out.push_str(&core);
} else {
let pad = width - len;
if left {
out.push_str(sign);
out.push_str(&core);
out.extend(std::iter::repeat(' ').take(pad));
} else if zero && conv != 's' {
out.push_str(sign);
out.extend(std::iter::repeat('0').take(pad));
out.push_str(&core);
} else {
out.extend(std::iter::repeat(' ').take(pad));
out.push_str(sign);
out.push_str(&core);
}
}
}
rettv.vval = v_string(out);
}
pub fn float_op_wrapper(argvars: &[typval_T], rettv: &mut typval_T, op: fn(f64) -> f64) {
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(op(tv_get_float(&argvars[0])));
}
pub fn f_pow(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(tv_get_float(&argvars[0]).powf(tv_get_float(&argvars[1])));
}
pub fn f_and(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval =
v_number(tv_get_number_chk(&argvars[0], None) & tv_get_number_chk(&argvars[1], None));
}
pub fn f_or(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval =
v_number(tv_get_number_chk(&argvars[0], None) | tv_get_number_chk(&argvars[1], None));
}
pub fn f_xor(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval =
v_number(tv_get_number_chk(&argvars[0], None) ^ tv_get_number_chk(&argvars[1], None));
}
pub fn f_invert(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(!tv_get_number_chk(&argvars[0], None));
}
pub fn f_insert(argvars: &[typval_T], rettv: &mut typval_T) {
let before_arg = argvars.get(2).filter(|t| t.v_type != VAR_UNKNOWN);
if let (VAR_BLOB, v_blob(b)) = (argvars[0].v_type, &argvars[0].vval) {
let Some(b) = b else { return };
let len = b.borrow().bv_ga.len() as varnumber_T;
let before = before_arg.map_or(0, |t| tv_get_number_chk(t, None));
if before < 0 || before > len {
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[2])
));
return;
}
let val = tv_get_number_chk(&argvars[1], None);
if !(0..=255).contains(&val) {
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[1])
));
return;
}
b.borrow_mut().bv_ga.insert(before as usize, val as u8);
*rettv = argvars[0].clone();
return;
}
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
(VAR_LIST, _) => {
*rettv = argvars[0].clone();
return;
}
_ => {
emsg("E897: List or Blob required");
return;
}
};
let mut lb = l.borrow_mut();
let len = lb.lv_len as varnumber_T;
let orig = before_arg.map_or(0, |t| tv_get_number_chk(t, None));
let idx = if orig < 0 { orig + len } else { orig };
if orig != len && (idx < 0 || idx >= len) {
crate::ported::message::semsg(&format!("E684: list index out of range: {orig}"));
return;
}
lb.lv_items.insert(
idx.clamp(0, len) as usize,
crate::ported::eval::typval_defs_h::listitem_T {
li_tv: argvars[1].clone(),
},
);
lb.lv_len = lb.lv_items.len() as i32;
drop(lb);
*rettv = argvars[0].clone();
}
pub fn f_copy(argvars: &[typval_T], rettv: &mut typval_T) {
match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => {
let items: Vec<_> = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
let out = tv_list_alloc_ret(rettv, items.len() as isize);
let mut ob = out.borrow_mut();
for tv in items {
tv_list_append_tv(&mut ob, tv);
}
}
(VAR_DICT, v_dict(Some(d))) => {
let pairs: Vec<_> = d
.borrow()
.dv_hashtab
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let out = crate::ported::eval::typval::tv_dict_alloc_ret(rettv);
let mut ob = out.borrow_mut();
for (k, v) in pairs {
tv_dict_add_tv(&mut ob, &k, v);
}
}
_ => *rettv = argvars[0].clone(),
}
}
pub fn f_items(argvars: &[typval_T], rettv: &mut typval_T) {
use crate::ported::eval::typval::{
tv_blob2items, tv_dict2items, tv_list2items, tv_string2items,
};
match argvars[0].v_type {
VAR_STRING => tv_string2items(argvars, rettv),
VAR_LIST => tv_list2items(argvars, rettv),
VAR_BLOB => tv_blob2items(argvars, rettv),
VAR_DICT => tv_dict2items(argvars, rettv),
_ => emsg("E1225: List, Dictionary, Blob or String required for argument 1"),
}
}
pub fn f_matchlist(argvars: &[typval_T], rettv: &mut typval_T) {
match find_some_match(argvars) {
Some(m) => {
let l = tv_list_alloc_ret(rettv, m.groups.len() as isize);
let mut lb = l.borrow_mut();
for g in &m.groups {
tv_list_append_string(&mut lb, g);
}
}
None => {
tv_list_alloc_ret(rettv, 0);
}
}
}
pub fn f_matchend(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(find_some_match(argvars).map_or(-1, |m| m.list_idx.unwrap_or(m.end)));
}
pub fn f_escape(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let chars: Vec<char> = tv_get_string(&argvars[1]).chars().collect();
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if chars.contains(&c) {
out.push('\\');
}
out.push(c);
}
rettv.v_type = VAR_STRING;
rettv.vval = v_string(out);
}
pub fn f_list2str(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let mut out = String::new();
if let (VAR_LIST, v_list(Some(l))) = (argvars[0].v_type, &argvars[0].vval) {
for it in &l.borrow().lv_items {
if let Some(c) = char::from_u32(tv_get_number_chk(&it.li_tv, None) as u32) {
out.push(c);
}
}
}
rettv.vval = v_string(out);
}
fn flatten_common(argvars: &[typval_T], rettv: &mut typval_T, make_copy: bool) {
let mut error = false;
if argvars[0].v_type != VAR_LIST {
emsg("E686: Argument of flatten() must be a List");
return;
}
let maxdepth = if argvars.len() < 2 {
999999
} else {
let d = tv_get_number_chk(&argvars[1], Some(&mut error));
if error {
return;
}
if d < 0 {
emsg("E900: maxdepth must be non-negative number");
return;
}
d
};
rettv.v_type = VAR_LIST;
let list = match &argvars[0].vval {
v_list(Some(l)) => l.clone(),
_ => {
rettv.vval = v_list(None);
return;
}
};
let list = if make_copy {
tv_list_copy(&list, false)
} else {
tv_list_ref(&mut list.borrow_mut());
list
};
rettv.vval = v_list(Some(list.clone()));
let len = tv_list_len(&list.borrow()) as i64;
tv_list_flatten(&mut list.borrow_mut(), len, maxdepth as i64);
}
pub fn f_flatten(argvars: &[typval_T], rettv: &mut typval_T) {
flatten_common(argvars, rettv, false);
}
pub fn f_flattennew(argvars: &[typval_T], rettv: &mut typval_T) {
flatten_common(argvars, rettv, true);
}
pub(crate) fn var_item_copy(from: &typval_T) -> typval_T {
match (from.v_type, &from.vval) {
(VAR_LIST, v_list(Some(l))) => {
let items: Vec<typval_T> = l
.borrow()
.lv_items
.iter()
.map(|it| var_item_copy(&it.li_tv))
.collect();
let out = crate::ported::eval::typval::tv_list_alloc(items.len() as isize);
{
let mut ob = out.borrow_mut();
for tv in items {
tv_list_append_tv(&mut ob, tv);
}
}
typval_T {
v_type: VAR_LIST,
v_lock: crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(out)),
}
}
(VAR_DICT, v_dict(Some(d))) => {
let pairs: Vec<(String, typval_T)> = d
.borrow()
.dv_hashtab
.iter()
.map(|(k, v)| (k.clone(), var_item_copy(v)))
.collect();
let out = crate::ported::eval::typval::tv_dict_alloc();
{
let mut ob = out.borrow_mut();
for (k, v) in pairs {
tv_dict_add_tv(&mut ob, &k, v);
}
}
typval_T {
v_type: VAR_DICT,
v_lock: crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(out)),
}
}
_ => from.clone(),
}
}
pub fn f_deepcopy(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = var_item_copy(&argvars[0]);
}
pub fn f_fmod(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(tv_get_float(&argvars[0]) % tv_get_float(&argvars[1]));
}
pub fn f_atan2(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(tv_get_float(&argvars[0]).atan2(tv_get_float(&argvars[1])));
}
pub fn f_json_encode(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(crate::ported::eval::encode::encode_tv2json(&argvars[0]));
}
pub fn f_json_decode(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
match crate::ported::eval::decode::json_decode_string(&s) {
Some(v) => *rettv = v,
None => emsg("E491: JSON decode error"),
}
}
pub fn f_matchstrpos(argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 3);
let mut lb = l.borrow_mut();
match find_some_match(argvars) {
Some(m) => {
let sub = m.groups.into_iter().next().unwrap_or_default();
tv_list_append_string(&mut lb, &sub);
if let Some(idx) = m.list_idx {
tv_list_append_number(&mut lb, idx);
}
tv_list_append_number(&mut lb, m.start);
tv_list_append_number(&mut lb, m.end);
}
None => {
tv_list_append_string(&mut lb, "");
tv_list_append_number(&mut lb, -1);
tv_list_append_number(&mut lb, -1);
}
}
}
pub fn f_getenv(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
match std::env::var(&name) {
Ok(p) => {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(p);
}
Err(_) => {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
}
}
pub fn f_setenv(argvars: &[typval_T], _rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
if argvars[1].v_type == VAR_SPECIAL {
std::env::remove_var(&name);
} else {
std::env::set_var(&name, tv_get_string(&argvars[1]));
}
}
pub fn f_shellescape(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for c in s.chars() {
if c == '\'' {
out.push_str("'\\''");
} else {
out.push(c);
}
}
out.push('\'');
rettv.v_type = VAR_STRING;
rettv.vval = v_string(out);
}
pub fn f_isinf(argvars: &[typval_T], rettv: &mut typval_T) {
if let (VAR_FLOAT, v_float(f)) = (argvars[0].v_type, &argvars[0].vval) {
if f.is_infinite() {
rettv.vval = v_number(if *f > 0.0 { 1 } else { -1 });
}
}
}
pub fn f_isnan(argvars: &[typval_T], rettv: &mut typval_T) {
let is = matches!((argvars[0].v_type, &argvars[0].vval), (VAR_FLOAT, v_float(f)) if f.is_nan());
rettv.vval = v_number(is as varnumber_T);
}
pub fn f_getpid(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(std::process::id() as varnumber_T);
}
pub fn f_localtime(_argvars: &[typval_T], rettv: &mut typval_T) {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as varnumber_T)
.unwrap_or(0);
rettv.vval = v_number(secs);
}
pub fn f_soundfold(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(tv_get_string(&argvars[0]));
}
fn list2proftime(arg: &typval_T, tm: &mut proftime_T) -> i32 {
let l = match (arg.v_type, &arg.vval) {
(VAR_LIST, v_list(Some(l))) => l,
_ => return FAIL,
};
let lb = l.borrow();
if tv_list_len(&lb) != 2 {
return FAIL;
}
let mut error = false;
let n1 = tv_list_find_nr(&lb, 0, Some(&mut error));
let n2 = tv_list_find_nr(&lb, 1, Some(&mut error));
if error {
return FAIL;
}
let high = (n1 as i32) as u32;
let low = (n2 as i32) as u32;
*tm = ((high as u64) << 32) | (low as u64);
OK
}
pub fn f_reltime(argvars: &[typval_T], rettv: &mut typval_T) {
let mut res: proftime_T = 0;
let mut start: proftime_T = 0;
if argvars.is_empty() {
res = profile_start();
} else if argvars.len() == 1 {
if list2proftime(&argvars[0], &mut res) == FAIL {
return;
}
res = profile_end(res);
} else {
if list2proftime(&argvars[0], &mut start) == FAIL
|| list2proftime(&argvars[1], &mut res) == FAIL
{
return;
}
res = profile_sub(res, start);
}
let high = ((res >> 32) as u32) as i32 as varnumber_T;
let low = (res as u32) as i32 as varnumber_T;
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, high);
tv_list_append_number(&mut lb, low);
}
pub fn f_reltimestr(argvars: &[typval_T], rettv: &mut typval_T) {
let mut tm: proftime_T = 0;
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
if list2proftime(&argvars[0], &mut tm) == OK {
rettv.vval = v_string(profile_msg(tm));
}
}
pub fn f_reltimefloat(argvars: &[typval_T], rettv: &mut typval_T) {
let mut tm: proftime_T = 0;
rettv.v_type = VAR_FLOAT;
rettv.vval = v_float(0.0);
if list2proftime(&argvars[0], &mut tm) == OK {
rettv.vval = v_float(profile_signed(tm) as f64 / 1_000_000_000.0);
}
}
fn reduce_list(argvars: &[typval_T], expr: &typval_T, rettv: &mut typval_T) {
let l = match &argvars[0].vval {
v_list(Some(l)) => l.clone(),
_ => return,
};
let call = |acc: &typval_T, item: &typval_T| -> Option<typval_T> {
CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(expr, &[acc.clone(), item.clone()]))
};
let items: Vec<typval_T> = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
let start = if argvars.len() < 3 {
if items.is_empty() {
emsg("E998: Reduce of an empty List with no initial value");
return;
}
*rettv = items[0].clone();
1
} else {
*rettv = argvars[2].clone();
0
};
for item in items.iter().skip(start) {
match call(rettv, item) {
Some(r) => *rettv = r,
None => return,
}
}
}
fn reduce_string(argvars: &[typval_T], expr: &typval_T, rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let call = |acc: &typval_T, item: &typval_T| -> Option<typval_T> {
CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(expr, &[acc.clone(), item.clone()]))
};
let chars: Vec<char> = s.chars().collect();
let start = if argvars.len() < 3 {
if chars.is_empty() {
emsg("E998: Reduce of an empty String with no initial value");
return;
}
*rettv = typval_T {
v_type: VAR_STRING,
v_lock: crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED,
vval: v_string(chars[0].to_string()),
};
1
} else {
if tv_check_for_string_arg(argvars, 2) == FAIL {
return;
}
*rettv = argvars[2].clone();
0
};
for ch in chars.iter().skip(start) {
let item = typval_T {
v_type: VAR_STRING,
v_lock: crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED,
vval: v_string(ch.to_string()),
};
match call(rettv, &item) {
Some(r) => *rettv = r,
None => return,
}
}
}
fn reduce_blob(argvars: &[typval_T], expr: &typval_T, rettv: &mut typval_T) {
let b = match &argvars[0].vval {
v_blob(Some(b)) => b.clone(),
_ => return,
};
let call = |acc: &typval_T, item: &typval_T| -> Option<typval_T> {
CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(expr, &[acc.clone(), item.clone()]))
};
let len = tv_blob_len(&b.borrow());
let start = if argvars.len() < 3 {
if len == 0 {
emsg("E998: Reduce of an empty Blob with no initial value");
return;
}
rettv.v_type = VAR_NUMBER;
rettv.vval = v_number(tv_blob_get(&b.borrow(), 0) as varnumber_T);
1
} else {
if tv_check_for_number_arg(argvars, 2) == FAIL {
return;
}
*rettv = argvars[2].clone();
0
};
let mut i = start;
while i < len {
let item = typval_T {
v_type: VAR_NUMBER,
v_lock: crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED,
vval: v_number(tv_blob_get(&b.borrow(), i) as varnumber_T),
};
match call(rettv, &item) {
Some(r) => *rettv = r,
None => return,
}
i += 1;
}
}
pub fn f_reduce(argvars: &[typval_T], rettv: &mut typval_T) {
if !matches!(argvars[0].v_type, VAR_STRING | VAR_LIST | VAR_BLOB) {
emsg("E1098: String, List or Blob required");
return;
}
let func_name = match (argvars[1].v_type, &argvars[1].vval) {
(VAR_PARTIAL, v_partial(Some(p))) => p.pt_name.clone(),
_ => tv_get_string(&argvars[1]),
};
if func_name.is_empty() {
emsg("E1132: Missing function argument");
return;
}
match argvars[0].v_type {
VAR_LIST => reduce_list(argvars, &argvars[1], rettv),
VAR_STRING => reduce_string(argvars, &argvars[1], rettv),
_ => reduce_blob(argvars, &argvars[1], rettv),
}
}
pub fn f_dictwatcheradd(argvars: &[typval_T], _rettv: &mut typval_T) {
if argvars[0].v_type != VAR_DICT {
emsg("E475: Invalid argument: dict");
return;
}
let d = match &argvars[0].vval {
v_dict(Some(d)) => d.clone(),
_ => return, };
if argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER {
emsg("E475: Invalid argument: key");
return;
}
let key_pattern = match tv_get_string_chk(&argvars[1]) {
Some(k) => k,
None => return,
};
let mut callback = Callback::None;
if !callback_from_typval(&mut callback, &argvars[2]) {
emsg("E475: Invalid argument: funcref");
return;
}
tv_dict_watcher_add(&d, &key_pattern, callback);
}
pub fn f_dictwatcherdel(argvars: &[typval_T], _rettv: &mut typval_T) {
if argvars[0].v_type != VAR_DICT {
emsg("E475: Invalid argument: dict");
return;
}
if argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING {
emsg("E475: Invalid argument: funcref");
return;
}
let key_pattern = match tv_get_string_chk(&argvars[1]) {
Some(k) => k,
None => return,
};
let mut callback = Callback::None;
if !callback_from_typval(&mut callback, &argvars[2]) {
return;
}
let d = match &argvars[0].vval {
v_dict(Some(d)) => d.clone(),
_ => return,
};
if !tv_dict_watcher_remove(&d, &key_pattern, &callback) {
emsg("Couldn't find a watcher matching key and callback");
}
}
pub fn f_strftime(argvars: &[typval_T], rettv: &mut typval_T) {
use nix::libc;
rettv.v_type = VAR_STRING;
let p = tv_get_string(&argvars[0]);
let seconds: libc::time_t = if argvars.len() < 2 {
unsafe { libc::time(std::ptr::null_mut()) }
} else {
tv_get_number(&argvars[1]) as libc::time_t
};
let curtime = match os_localtime_r(&seconds) {
Some(t) => t,
None => {
rettv.vval = v_string("(Invalid)".to_string());
return;
}
};
let fmt = match std::ffi::CString::new(p) {
Ok(c) => c,
Err(_) => {
rettv.vval = v_string(String::new());
return;
}
};
let mut buf = [0u8; 256];
let n = unsafe {
libc::strftime(
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
fmt.as_ptr(),
&curtime,
)
};
let s = if n == 0 {
String::new()
} else {
String::from_utf8_lossy(&buf[..n]).into_owned()
};
rettv.vval = v_string(s);
}
pub fn f_strptime(argvars: &[typval_T], rettv: &mut typval_T) {
use nix::libc;
let mut tmval: libc::tm = unsafe { std::mem::zeroed() };
tmval.tm_isdst = -1;
let fmt = tv_get_string_buf(&argvars[0]);
let str = tv_get_string_buf(&argvars[1]);
let secs = if os_strptime(&str, &fmt, &mut tmval) {
unsafe { libc::mktime(&mut tmval) }
} else {
-1
};
rettv.vval = v_number(if secs == -1 { 0 } else { secs as varnumber_T });
}
pub fn f_sha256(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
if argvars[0].v_type == VAR_BLOB {
let bytes: Vec<u8> = match &argvars[0].vval {
v_blob(Some(b)) => b.borrow().bv_ga.clone(),
_ => Vec::new(),
};
rettv.vval = v_string(sha256_bytes(&bytes, None));
} else {
let p = tv_get_string(&argvars[0]);
rettv.vval = v_string(sha256_bytes(p.as_bytes(), None));
}
}
fn init_srand(x: &mut u32) {
*x = os_hrtime() as u32;
*x ^= os_get_pid() as u32;
}
fn splitmix32(x: &mut u32) -> u32 {
*x = x.wrapping_add(0x9e37_79b9);
let mut z = *x;
z = (z ^ (z >> 16)).wrapping_mul(0x85eb_ca6b);
z = (z ^ (z >> 13)).wrapping_mul(0xc2b2_ae35);
z ^ (z >> 16)
}
fn shuffle_xoshiro128starstar(x: &mut u32, y: &mut u32, z: &mut u32, w: &mut u32) -> u32 {
let result = y.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
let t = *y << 9;
*z ^= *x;
*w ^= *y;
*y ^= *z;
*x ^= *w;
*z ^= t;
*w = w.rotate_left(11);
result
}
thread_local! {
static RAND_GLOBAL: std::cell::Cell<Option<[u32; 4]>> = const { std::cell::Cell::new(None) };
}
pub fn f_srand(argvars: &[typval_T], rettv: &mut typval_T) {
let mut x: u32 = 0;
let l = tv_list_alloc_ret(rettv, 4);
if argvars.is_empty() {
init_srand(&mut x);
} else {
let mut error = false;
x = tv_get_number_chk(&argvars[0], Some(&mut error)) as u32;
if error {
return;
}
}
let mut lb = l.borrow_mut();
for _ in 0..4 {
let v = splitmix32(&mut x) as varnumber_T;
tv_list_append_number(&mut lb, v);
}
}
pub fn f_rand(argvars: &[typval_T], rettv: &mut typval_T) {
let result: u32;
if argvars.is_empty() {
let mut s = RAND_GLOBAL.with(|c| c.get()).unwrap_or_else(|| {
let mut x: u32 = 0;
init_srand(&mut x);
[
splitmix32(&mut x),
splitmix32(&mut x),
splitmix32(&mut x),
splitmix32(&mut x),
]
});
let [mut gx, mut gy, mut gz, mut gw] = s;
result = shuffle_xoshiro128starstar(&mut gx, &mut gy, &mut gz, &mut gw);
s = [gx, gy, gz, gw];
RAND_GLOBAL.with(|c| c.set(Some(s)));
} else if let (VAR_LIST, v_list(Some(l))) = (argvars[0].v_type, &argvars[0].vval) {
let mut lb = l.borrow_mut();
let nums: Option<[u32; 4]> = if lb.lv_items.len() == 4 {
let mut a = [0u32; 4];
let mut ok = true;
for (i, it) in lb.lv_items.iter().enumerate() {
match (it.li_tv.v_type, &it.li_tv.vval) {
(VAR_NUMBER, v_number(n)) => a[i] = *n as u32,
_ => ok = false,
}
}
ok.then_some(a)
} else {
None
};
match nums {
Some([mut x, mut y, mut z, mut w]) => {
result = shuffle_xoshiro128starstar(&mut x, &mut y, &mut z, &mut w);
lb.lv_items[0].li_tv.vval = v_number(x as varnumber_T);
lb.lv_items[1].li_tv.vval = v_number(y as varnumber_T);
lb.lv_items[2].li_tv.vval = v_number(z as varnumber_T);
lb.lv_items[3].li_tv.vval = v_number(w as varnumber_T);
}
None => {
drop(lb);
emsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[0])
));
rettv.v_type = VAR_NUMBER;
rettv.vval = v_number(-1);
return;
}
}
} else {
emsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[0])
));
rettv.v_type = VAR_NUMBER;
rettv.vval = v_number(-1);
return;
}
rettv.v_type = VAR_NUMBER;
rettv.vval = v_number(result as varnumber_T);
}
fn getreg_get_regname(argvars: &[typval_T]) -> Option<char> {
let s = if argvars.is_empty() {
get_vim_var_str(VV_REG)
} else {
tv_get_string_chk(&argvars[0])?
};
let c = s.chars().next().unwrap_or('\0');
Some(if c == '\0' { '"' } else { c })
}
pub fn f_getreg(argvars: &[typval_T], rettv: &mut typval_T) {
let regname = match getreg_get_regname(argvars) {
Some(r) => r,
None => return,
};
let return_list = argvars.len() > 2 && tv_get_number_chk(&argvars[2], None) != 0;
let lines = get_reg_contents(regname).unwrap_or_default();
if return_list {
let l = tv_list_alloc_ret(rettv, 0);
for line in lines {
tv_list_append_string(&mut l.borrow_mut(), &line);
}
} else {
let mut s = lines.join("\n");
if get_reg_type(regname).0 == MotionType::LineWise {
s.push('\n');
}
*rettv = typval_T::from(s);
}
}
pub fn f_getregtype(argvars: &[typval_T], rettv: &mut typval_T) {
let regname = match getreg_get_regname(argvars) {
Some(r) => r,
None => return,
};
let (t, len) = get_reg_type(regname);
*rettv = typval_T::from(format_reg_type(t, len));
}
pub fn f_getreginfo(argvars: &[typval_T], rettv: &mut typval_T) {
let mut regname = match getreg_get_regname(argvars) {
Some(r) => r,
None => return,
};
if regname == '@' {
regname = '"';
}
let d = tv_dict_alloc_ret(rettv);
let lines = match get_reg_contents(regname) {
Some(l) => l,
None => return,
};
let lst = tv_list_alloc(0);
for line in lines {
tv_list_append_string(&mut lst.borrow_mut(), &line);
}
let mk = |t, v| typval_T {
v_type: t,
v_lock: crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED,
vval: v,
};
tv_dict_add_tv(
&mut d.borrow_mut(),
"regcontents",
mk(VAR_LIST, v_list(Some(lst))),
);
let (t, len) = get_reg_type(regname);
tv_dict_add_tv(
&mut d.borrow_mut(),
"regtype",
typval_T::from(format_reg_type(t, len)),
);
if regname == '"' {
tv_dict_add_tv(
&mut d.borrow_mut(),
"points_to",
typval_T::from("\"".to_string()),
);
} else {
tv_dict_add_tv(
&mut d.borrow_mut(),
"isunnamed",
mk(VAR_BOOL, v_bool(kBoolVarFalse)),
);
}
}
pub fn f_setreg(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(1 as varnumber_T); let strreg = match tv_get_string_chk(&argvars[0]) {
Some(s) => s,
None => return,
};
let mut regname = strreg.chars().next().unwrap_or('\0');
if regname == '\0' || regname == '@' {
regname = '"';
}
let mut yank_type: Option<MotionType> = None;
let contents: typval_T = if argvars[1].v_type == VAR_DICT {
if let v_dict(Some(dd)) = &argvars[1].vval {
let d = dd.borrow();
if let Some(rt) = d.dv_hashtab.get("regtype") {
let rt = tv_get_string(rt);
if let Some(c) = rt.bytes().next() {
yank_type = get_yank_type(c, 0);
}
}
d.dv_hashtab.get("regcontents").cloned().unwrap_or_default()
} else {
typval_T::default()
}
} else {
argvars[1].clone()
};
let mut append = false;
if argvars.len() > 2 {
let opt = tv_get_string(&argvars[2]);
for c in opt.bytes() {
match c {
b'a' | b'A' => append = true,
b'u' | b'"' => {}
_ => {
if let Some(t) = get_yank_type(c, 0) {
yank_type = Some(t);
}
}
}
}
}
match (contents.v_type, &contents.vval) {
(VAR_LIST, v_list(Some(l))) => {
let lines: Vec<String> = l
.borrow()
.lv_items
.iter()
.map(|it| tv_get_string(&it.li_tv))
.collect();
write_reg_contents_lst(
regname,
lines,
yank_type.unwrap_or(MotionType::LineWise),
append,
);
}
_ => {
let sval = tv_get_string(&contents);
let mtype = yank_type.unwrap_or_else(|| {
if sval.ends_with('\n') {
MotionType::LineWise
} else {
MotionType::CharWise
}
});
write_reg_contents(regname, &sval, mtype, append);
}
}
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_reg_recording(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_reg_executing(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_reg_recorded(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_gettext(argvars: &[typval_T], rettv: &mut typval_T) {
if tv_check_for_string_arg(argvars, 0) == FAIL {
return;
}
*rettv = typval_T::from(tv_get_string(&argvars[0]));
}
pub fn f_garbagecollect(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn f_funcref(argvars: &[typval_T], rettv: &mut typval_T) {
f_function(argvars, rettv);
}
pub fn f_id(argvars: &[typval_T], rettv: &mut typval_T) {
let addr: usize = match &argvars[0].vval {
v_list(Some(l)) => std::rc::Rc::as_ptr(l) as *const () as usize,
v_dict(Some(d)) => std::rc::Rc::as_ptr(d) as *const () as usize,
v_blob(Some(b)) => std::rc::Rc::as_ptr(b) as *const () as usize,
v_partial(Some(p)) => std::rc::Rc::as_ptr(p) as *const () as usize,
_ => 0,
};
*rettv = typval_T::from(if addr == 0 {
String::new()
} else {
format!("{addr:#018x}")
});
}
fn indexof_eval_expr(expr: &typval_T, key: &typval_T, val: &typval_T) -> bool {
FILTER_MAP_EVAL_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(expr, key, val))
.map(|r| tv_get_bool(&r) != 0)
.unwrap_or(false)
}
fn indexof_blob(
b: Option<&std::rc::Rc<std::cell::RefCell<blob_T>>>,
startidx: varnumber_T,
expr: &typval_T,
) -> varnumber_T {
let Some(b) = b else { return -1 };
let bytes = b.borrow().bv_ga.clone();
let blen = bytes.len() as varnumber_T;
let mut idx = startidx;
if idx < 0 {
idx += blen;
if idx < 0 {
idx = 0;
}
}
while idx < blen {
let key = typval_T::from(idx);
let val = typval_T::from(bytes[idx as usize] as varnumber_T);
if indexof_eval_expr(expr, &key, &val) {
return idx;
}
idx += 1;
}
-1
}
fn indexof_list(
l: Option<&std::rc::Rc<std::cell::RefCell<list_T>>>,
startidx: varnumber_T,
expr: &typval_T,
) -> varnumber_T {
let Some(l) = l else { return -1 };
let items: Vec<typval_T> = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
let len = items.len() as varnumber_T;
let start = if startidx == 0 {
0
} else {
let s = if startidx < 0 {
startidx + len
} else {
startidx
};
if s < 0 || s >= len {
return -1;
}
s
};
let mut idx = start;
while idx < len {
let key = typval_T::from(idx);
if indexof_eval_expr(expr, &key, &items[idx as usize]) {
return idx;
}
idx += 1;
}
-1
}
pub fn f_indexof(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
let expr = &argvars[1];
if expr.v_type == VAR_STRING && tv_get_string(expr).is_empty() {
return;
}
let startidx = match argvars.get(2) {
Some(d) if d.v_type == VAR_DICT => match &d.vval {
v_dict(Some(dd)) => dd
.borrow()
.dv_hashtab
.get("startidx")
.map(tv_get_number)
.unwrap_or(0),
_ => 0,
},
_ => 0,
};
let found = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(l)) => indexof_list(l.as_ref(), startidx, expr),
(VAR_BLOB, v_blob(b)) => indexof_blob(b.as_ref(), startidx, expr),
_ => -1,
};
*rettv = typval_T::from(found);
}
fn get_matches_in_str(
s: &str,
pat: &str,
ic: bool,
mlist: &std::rc::Rc<std::cell::RefCell<list_T>>,
idx: varnumber_T,
submatches: bool,
matchbuf: bool,
) {
use crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED;
let chars: Vec<char> = s.chars().collect();
let mut from = 0usize;
while let Some((cstart, cend, groups)) =
crate::viml_regex::regex_search_nth(pat, s, ic, from, 1)
{
let byteidx: usize = chars[..cstart as usize].iter().map(|c| c.len_utf8()).sum();
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_tv(
&mut db,
if matchbuf { "lnum" } else { "idx" },
typval_T::from(idx),
);
tv_dict_add_tv(&mut db, "byteidx", typval_T::from(byteidx as varnumber_T));
tv_dict_add_tv(&mut db, "text", typval_T::from(groups[0].clone()));
if submatches {
let sub = tv_list_alloc(0);
{
let mut sb = sub.borrow_mut();
for g in groups.iter().take(10).skip(1) {
tv_list_append_string(&mut sb, g);
}
}
tv_dict_add_tv(
&mut db,
"submatches",
typval_T {
v_type: VAR_LIST,
v_lock: VAR_UNLOCKED,
vval: v_list(Some(sub)),
},
);
}
}
tv_list_append_tv(
&mut mlist.borrow_mut(),
typval_T {
v_type: VAR_DICT,
v_lock: VAR_UNLOCKED,
vval: v_dict(Some(d)),
},
);
from = if cend > cstart {
cend as usize
} else {
cstart as usize + 1
};
if from > chars.len() {
break;
}
}
}
pub fn f_matchstrlist(argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 0);
let list = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
(VAR_LIST, v_list(None)) => return,
_ => {
emsg("E1211: List required");
return;
}
};
let pat = tv_get_string(&argvars[1]);
let ic = tv_get_number(&get_option_value("ignorecase")) != 0;
let submatches = argvars.get(2).is_some_and(|d| match &d.vval {
v_dict(Some(dd)) => {
dd.borrow()
.dv_hashtab
.get("submatches")
.map(tv_get_bool)
.unwrap_or(0)
!= 0
}
_ => false,
});
let items: Vec<typval_T> = list
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
for (idx, item) in items.iter().enumerate() {
let s = tv_get_string(item);
get_matches_in_str(&s, &pat, ic, &l, idx as varnumber_T, submatches, false);
}
}
pub fn f_fnameescape(argvars: &[typval_T], rettv: &mut typval_T) {
const ESC: &[u8] = b" \t\n*?[{`$\\%#'\"|!<";
let name = tv_get_string(&argvars[0]);
let mut out = String::with_capacity(name.len() + 2);
for (i, b) in name.bytes().enumerate() {
if ESC.contains(&b) || (i == 0 && (b == b'+' || b == b'>')) {
out.push('\\');
}
out.push(b as char);
}
*rettv = typval_T::from(out);
}
pub fn f_shiftwidth(_argvars: &[typval_T], rettv: &mut typval_T) {
let mut sw = tv_get_number(&get_option_value("shiftwidth"));
if sw == 0 {
sw = tv_get_number(&get_option_value("tabstop"));
}
*rettv = typval_T::from(sw);
}
pub fn f_mode(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from("n".to_string());
}
pub fn f_state(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_visualmode(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_pumvisible(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_wildmenumode(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_did_filetype(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_eventhandler(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_hlexists(argvars: &[typval_T], rettv: &mut typval_T) {
let name = argvars.first().map(tv_get_string).unwrap_or_default();
*rettv = typval_T::from(hl_exists(&name) as varnumber_T);
}
pub fn f_windowsversion(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_getfontname(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_foreground(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_prompt_getprompt(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_pum_getpos(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_dict_alloc_ret(rettv);
}
pub fn f_serverlist(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
fn getpos_both(rettv: &mut typval_T, getcurpos: bool) {
let len = 4 + getcurpos as isize;
let l = tv_list_alloc_ret(rettv, len);
let mut lb = l.borrow_mut();
for _ in 0..len {
tv_list_append_number(&mut lb, 0);
}
}
thread_local! {
static CURBUF: std::cell::RefCell<Vec<String>> =
const { std::cell::RefCell::new(Vec::new()) };
static CURPOS: std::cell::RefCell<(varnumber_T, varnumber_T, varnumber_T)> =
const { std::cell::RefCell::new((1, 1, 1)) };
}
fn curbuf_len() -> varnumber_T {
if let Some(n) = crate::fusevm_bridge::editor_line_count() {
return n.max(1);
}
CURBUF.with(|b| b.borrow().len().max(1) as varnumber_T)
}
fn tv_get_lnum(tv: &typval_T) -> varnumber_T {
if tv.v_type == VAR_STRING {
let s = tv_get_string(tv);
match s.as_str() {
"." => crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow())).0,
"$" | "w$" => curbuf_len(),
"w0" => 1,
_ if s.starts_with('\'') => {
s.chars().nth(1).and_then(getmark).map_or(0, |(l, _)| l)
}
_ => s.parse().unwrap_or(0),
}
} else {
tv_get_number(tv)
}
}
thread_local! {
static MARKS: std::cell::RefCell<std::collections::BTreeMap<char, (varnumber_T, varnumber_T)>> =
const { std::cell::RefCell::new(std::collections::BTreeMap::new()) };
}
fn setmark(name: char, lnum: varnumber_T, col: varnumber_T) {
MARKS.with(|m| {
m.borrow_mut().insert(name, (lnum, col));
});
}
fn getmark(name: char) -> Option<(varnumber_T, varnumber_T)> {
MARKS.with(|m| m.borrow().get(&name).copied())
}
fn get_buffer_lines(start: varnumber_T, end: varnumber_T) -> Vec<String> {
if let Some(v) = crate::fusevm_bridge::editor_get_lines(start, end) {
return v;
}
CURBUF.with(|b| {
let b = b.borrow();
if b.is_empty() {
return if start <= 1 && end >= 1 {
vec![String::new()]
} else {
Vec::new()
};
}
let len = b.len() as varnumber_T;
let (s, e) = (start.max(1), end.min(len));
if s > e {
return Vec::new();
}
(s..=e).map(|i| b[(i - 1) as usize].clone()).collect()
})
}
fn set_buffer_lines(lnum: varnumber_T, lines: Vec<String>, append: bool) -> varnumber_T {
if crate::fusevm_bridge::editor_host_active() {
return crate::fusevm_bridge::editor_set_lines(lnum, lines, append).unwrap_or(1);
}
CURBUF.with(|b| {
let mut b = b.borrow_mut();
if b.is_empty() {
b.push(String::new());
}
if append {
let pos = (lnum.max(0) as usize).min(b.len());
for (i, l) in lines.into_iter().enumerate() {
b.insert(pos + i, l);
}
0
} else {
if lnum < 1 {
return 1;
}
for (i, l) in lines.into_iter().enumerate() {
let idx = (lnum - 1) as usize + i;
if idx < b.len() {
b[idx] = l;
} else if idx == b.len() {
b.push(l);
} else {
break;
}
}
0
}
})
}
fn tv_lines_arg(tv: &typval_T) -> Vec<String> {
match (tv.v_type, &tv.vval) {
(VAR_LIST, v_list(Some(l))) => l
.borrow()
.lv_items
.iter()
.map(|it| tv_get_string(&it.li_tv))
.collect(),
_ => vec![tv_get_string(tv)],
}
}
pub fn set_cursorpos(lnum: varnumber_T, col: varnumber_T) {
let len = curbuf_len();
let l = lnum.clamp(1, len);
let linelen = get_buffer_lines(l, l)
.first()
.map_or(0, |s| s.len() as varnumber_T);
let c = col.clamp(1, linelen + 1);
if crate::fusevm_bridge::editor_set_cursor(l, c) {
return;
}
CURPOS.with(|p| {
let mut p = p.borrow_mut();
*p = (l, c, c);
});
}
pub fn f_getpos(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let (lnum, col) = if s == "." {
CURPOS.with(|c| {
let c = c.borrow();
(c.0, c.1)
})
} else if let Some(name) = s.strip_prefix('\'').and_then(|r| r.chars().next()) {
getmark(name).unwrap_or((0, 0))
} else {
(tv_get_lnum(&argvars[0]), 0)
};
let l = tv_list_alloc_ret(rettv, 4);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 0);
tv_list_append_number(&mut lb, lnum);
tv_list_append_number(&mut lb, col);
tv_list_append_number(&mut lb, 0);
}
pub fn f_getcharpos(argvars: &[typval_T], rettv: &mut typval_T) {
f_getpos(argvars, rettv);
}
pub fn f_getcurpos(_argvars: &[typval_T], rettv: &mut typval_T) {
let (lnum, col, curswant) = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
let l = tv_list_alloc_ret(rettv, 5);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 0);
tv_list_append_number(&mut lb, lnum);
tv_list_append_number(&mut lb, col);
tv_list_append_number(&mut lb, 0);
tv_list_append_number(&mut lb, curswant);
}
pub fn f_getcursorcharpos(_argvars: &[typval_T], rettv: &mut typval_T) {
getpos_both(rettv, true);
}
pub fn f_col(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let (lnum, ccol) = {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let col = match s.as_str() {
"." => ccol,
"$" => get_buffer_lines(lnum, lnum)
.first()
.map_or(1, |l| l.len() as varnumber_T + 1),
_ if s.starts_with('\'') => s.chars().nth(1).and_then(getmark).map_or(0, |(_, c)| c),
_ => 0,
};
*rettv = typval_T::from(col);
}
pub fn f_charcol(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let (lnum, ccol) = {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let col = match s.as_str() {
"." => ccol,
"$" => get_buffer_lines(lnum, lnum)
.first()
.map_or(1, |l| l.chars().count() as varnumber_T + 1),
_ => 0,
};
*rettv = typval_T::from(col);
}
pub fn f_line(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(tv_get_lnum(&argvars[0]));
}
pub fn f_virtcol(argvars: &[typval_T], rettv: &mut typval_T) {
let s = tv_get_string(&argvars[0]);
let want_list = argvars.len() > 1 && tv_get_bool(&argvars[1]) != 0;
let (lnum, ccol) = {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let line = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
let ts = {
let t = tv_get_number_chk(&get_option_value("tabstop"), None);
if t > 0 {
t as varnumber_T
} else {
8
}
};
let dollar = s == "$";
let target_byte = if dollar {
line.len()
} else if s == "." {
(ccol as usize).saturating_sub(1)
} else if let Some(name) = s.strip_prefix('\'').and_then(|r| r.chars().next()) {
(getmark(name).map_or(1, |(_, c)| c) as usize).saturating_sub(1)
} else {
(ccol as usize).saturating_sub(1)
};
let mut vcol: varnumber_T = 0;
for (bi, c) in line.char_indices() {
let w = if c == '\t' { ts - (vcol % ts) } else { 1 };
vcol += w;
if !dollar && bi >= target_byte {
break;
}
}
if dollar {
vcol += 1;
}
if want_list {
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, vcol);
tv_list_append_number(&mut lb, vcol);
} else {
*rettv = typval_T::from(vcol);
}
}
pub fn f_screenrow(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_screencol(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_screenchar(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_screenattr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_screenchars(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_screenstring(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_line2byte(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[0]);
let len = curbuf_len();
if lnum < 1 || lnum > len + 1 {
*rettv = typval_T::from(-1 as varnumber_T);
return;
}
let mut off: varnumber_T = 1;
for l in get_buffer_lines(1, lnum - 1) {
off += l.len() as varnumber_T + 1;
}
*rettv = typval_T::from(off);
}
pub fn f_byte2line(argvars: &[typval_T], rettv: &mut typval_T) {
let target = tv_get_number(&argvars[0]);
if target < 1 {
*rettv = typval_T::from(-1 as varnumber_T);
return;
}
let mut off: varnumber_T = 1;
let mut lnum: varnumber_T = 0;
for l in get_buffer_lines(1, curbuf_len()) {
lnum += 1;
off += l.len() as varnumber_T + 1;
if target < off {
*rettv = typval_T::from(lnum);
return;
}
}
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_nextnonblank(argvars: &[typval_T], rettv: &mut typval_T) {
let mut lnum = tv_get_lnum(&argvars[0]).max(1);
let len = curbuf_len();
while lnum <= len {
if !get_buffer_lines(lnum, lnum)[0].trim().is_empty() {
*rettv = typval_T::from(lnum);
return;
}
lnum += 1;
}
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_prevnonblank(argvars: &[typval_T], rettv: &mut typval_T) {
let mut lnum = tv_get_lnum(&argvars[0]).min(curbuf_len());
while lnum >= 1 {
if !get_buffer_lines(lnum, lnum)[0].trim().is_empty() {
*rettv = typval_T::from(lnum);
return;
}
lnum -= 1;
}
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_wordcount(_argvars: &[typval_T], rettv: &mut typval_T) {
let lines = get_buffer_lines(1, curbuf_len());
let (clnum, ccol) = {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let (mut bytes, mut chars, mut words) = (0i64, 0i64, 0i64);
let (mut cbytes, mut cchars, mut cwords) = (0i64, 0i64, 0i64);
for (i, line) in lines.iter().enumerate() {
let lnum = i as varnumber_T + 1;
let lwords = line.split_whitespace().count() as i64;
words += lwords;
if lnum < clnum {
cbytes += line.len() as i64 + 1;
cchars += line.chars().count() as i64 + 1;
cwords += lwords;
} else if lnum == clnum {
let upto = &line[..((ccol as usize).saturating_sub(1)).min(line.len())];
cbytes += upto.len() as i64;
cchars += upto.chars().count() as i64;
cwords += upto.split_whitespace().count() as i64;
}
bytes += line.len() as i64 + 1;
chars += line.chars().count() as i64 + 1;
}
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "bytes", bytes);
tv_dict_add_nr(&mut db, "chars", chars);
tv_dict_add_nr(&mut db, "words", words);
tv_dict_add_nr(&mut db, "cursor_bytes", cbytes);
tv_dict_add_nr(&mut db, "cursor_chars", cchars);
tv_dict_add_nr(&mut db, "cursor_words", cwords);
}
pub fn f_getjumplist(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 2);
let inner = tv_list_alloc(0);
let mut lb = l.borrow_mut();
tv_list_append_list(&mut lb, inner);
tv_list_append_number(&mut lb, 0);
}
pub fn f_getchangelist(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 2);
let inner = tv_list_alloc(0);
let mut lb = l.borrow_mut();
tv_list_append_list(&mut lb, inner);
tv_list_append_number(&mut lb, 0);
}
pub fn f_getmarklist(_argvars: &[typval_T], rettv: &mut typval_T) {
let marks: Vec<(char, (varnumber_T, varnumber_T))> =
MARKS.with(|m| m.borrow().iter().map(|(k, v)| (*k, *v)).collect());
let out = tv_list_alloc_ret(rettv, marks.len() as isize);
let mut ob = out.borrow_mut();
for (name, (lnum, col)) in marks {
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "mark", &format!("'{name}"));
let pos = tv_list_alloc(4);
{
let mut pb = pos.borrow_mut();
tv_list_append_number(&mut pb, 0);
tv_list_append_number(&mut pb, lnum);
tv_list_append_number(&mut pb, col);
tv_list_append_number(&mut pb, 0);
}
tv_dict_add_tv(
&mut db,
"pos",
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(pos)),
},
);
}
tv_list_append_tv(&mut ob, match_dict_val(d));
}
}
pub fn f_gettagstack(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_list(&mut db, "items", tv_list_alloc(0));
tv_dict_add_nr(&mut db, "length", 0);
tv_dict_add_nr(&mut db, "curidx", 0);
}
pub fn f_tagfiles(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_taglist(argvars: &[typval_T], rettv: &mut typval_T) {
if tv_get_string(&argvars[0]).is_empty() {
*rettv = typval_T::from(0 as varnumber_T);
return;
}
tv_list_alloc_ret(rettv, 0);
}
pub fn f_tabpagebuflist(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
thread_local! {
static LAST_SEARCH: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
}
fn line_match_from(pat: &str, line: &str, from: usize, ic: bool) -> Option<(usize, usize)> {
if from > line.len() {
return None;
}
let (_, s, e) = crate::viml_regex::regex_matchstrpos(pat, &line[from..], ic);
if s < 0 {
None
} else {
Some((from + s as usize, from + e as usize))
}
}
fn searchit(pat: &str, flags: &str, _stopline: varnumber_T) -> Option<(varnumber_T, varnumber_T)> {
LAST_SEARCH.with(|p| *p.borrow_mut() = pat.to_string());
let backward = flags.contains('b');
let nomove = flags.contains('n');
let accept = flags.contains('c');
let want_end = flags.contains('e');
let wrap = !flags.contains('W');
let ic = tv_get_number_chk(&get_option_value("ignorecase"), None) != 0;
let (clnum, ccol) = {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let lines = get_buffer_lines(1, curbuf_len());
let n = lines.len();
if n == 0 {
return None;
}
let cur = (clnum - 1).clamp(0, n as varnumber_T - 1) as usize;
let cbyte = (ccol - 1).max(0) as usize;
let mut order: Vec<(usize, Option<usize>, Option<usize>)> = Vec::new();
if !backward {
order.push((cur, Some(if accept { cbyte } else { cbyte + 1 }), None));
for i in cur + 1..n {
order.push((i, Some(0), None));
}
if wrap {
for i in 0..=cur {
order.push((i, Some(0), None));
}
}
} else {
order.push((cur, None, Some(if accept { cbyte + 1 } else { cbyte })));
for i in (0..cur).rev() {
order.push((i, None, Some(usize::MAX)));
}
if wrap {
for i in (cur..n).rev() {
order.push((i, None, Some(usize::MAX)));
}
}
}
for (li, fwd_from, back_before) in order {
let line = &lines[li];
if let Some(from) = fwd_from {
if let Some((s, e)) = line_match_from(pat, line, from, ic) {
let col = if want_end { e } else { s + 1 };
return finish_search(li, col, nomove, clnum, ccol);
}
} else if let Some(before) = back_before {
let mut found: Option<(usize, usize)> = None;
let mut scan = 0usize;
while let Some((s, e)) = line_match_from(pat, line, scan, ic) {
if s >= before {
break;
}
found = Some((s, e));
scan = if e > s { e } else { s + 1 };
}
if let Some((s, e)) = found {
let col = if want_end { e } else { s + 1 };
return finish_search(li, col, nomove, clnum, ccol);
}
}
}
None
}
fn finish_search(
li: usize,
col: usize,
nomove: bool,
_clnum: varnumber_T,
_ccol: varnumber_T,
) -> Option<(varnumber_T, varnumber_T)> {
let lnum = li as varnumber_T + 1;
let col = col as varnumber_T;
if !nomove {
set_cursorpos(lnum, col);
}
Some((lnum, col))
}
pub fn f_search(argvars: &[typval_T], rettv: &mut typval_T) {
let pat = tv_get_string(&argvars[0]);
let flags = argvars.get(1).map(tv_get_string).unwrap_or_default();
let stopline = argvars
.get(2)
.filter(|t| t.v_type != VAR_UNKNOWN)
.map(tv_get_number)
.unwrap_or(0);
let lnum = match searchit(&pat, &flags, stopline) {
Some((lnum, _)) => lnum,
None => 0,
};
*rettv = typval_T::from(lnum);
}
pub fn f_searchpos(argvars: &[typval_T], rettv: &mut typval_T) {
let pat = tv_get_string(&argvars[0]);
let flags = argvars.get(1).map(tv_get_string).unwrap_or_default();
let stopline = argvars
.get(2)
.filter(|t| t.v_type != VAR_UNKNOWN)
.map(tv_get_number)
.unwrap_or(0);
let (lnum, col) = searchit(&pat, &flags, stopline).unwrap_or((0, 0));
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, lnum);
tv_list_append_number(&mut lb, col);
}
fn do_searchpair(
start: &str,
middle: &str,
end: &str,
flags: &str,
) -> Option<(varnumber_T, varnumber_T)> {
let backward = flags.contains('b');
let nomove = flags.contains('n');
let ic = tv_get_number_chk(&get_option_value("ignorecase"), None) != 0;
let lines = get_buffer_lines(1, curbuf_len());
let n = lines.len();
let (clnum, ccol) = {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let cur = (clnum - 1).clamp(0, n as varnumber_T - 1) as usize;
let cbyte = (ccol - 1).max(0) as usize;
let m_at = |pat: &str, line: &str, col: usize| -> Option<usize> {
if pat.is_empty() || col > line.len() {
return None;
}
let (_, s, e) = crate::viml_regex::regex_matchstrpos(pat, &line[col..], ic);
if s == 0 {
Some((e - s).max(1) as usize)
} else {
None
}
};
let mut positions: Vec<(usize, usize)> = Vec::new();
#[allow(clippy::needless_range_loop)]
if !backward {
for li in cur..n {
let from = if li == cur { cbyte + 1 } else { 0 };
for col in from..=lines[li].len() {
positions.push((li, col));
}
}
} else {
for li in (0..=cur).rev() {
let to = if li == cur {
cbyte
} else {
lines[li].len() + 1
};
for col in (0..to).rev() {
positions.push((li, col));
}
}
}
let mut nest = 0i32;
let mut idx = 0usize;
while idx < positions.len() {
let (li, col) = positions[idx];
let line = &lines[li];
let (opener, closer) = if backward { (end, start) } else { (start, end) };
if let Some(len) = m_at(closer, line, col) {
if nest == 0 {
let lnum = li as varnumber_T + 1;
let c = col as varnumber_T + 1;
if !nomove {
set_cursorpos(lnum, c);
}
return Some((lnum, c));
}
nest -= 1;
idx += skip_cols(&positions, idx, len);
continue;
}
if let Some(len) = m_at(opener, line, col) {
nest += 1;
idx += skip_cols(&positions, idx, len);
continue;
}
if nest == 0 && m_at(middle, line, col).is_some() {
let lnum = li as varnumber_T + 1;
let c = col as varnumber_T + 1;
if !nomove {
set_cursorpos(lnum, c);
}
return Some((lnum, c));
}
idx += 1;
}
None
}
fn skip_cols(positions: &[(usize, usize)], idx: usize, len: usize) -> usize {
let (li, _) = positions[idx];
let mut n = 1;
while n < len && idx + n < positions.len() && positions[idx + n].0 == li {
n += 1;
}
n
}
pub fn f_searchpair(argvars: &[typval_T], rettv: &mut typval_T) {
let start = tv_get_string(&argvars[0]);
let middle = tv_get_string(&argvars[1]);
let end = tv_get_string(&argvars[2]);
let flags = argvars.get(3).map(tv_get_string).unwrap_or_default();
let lnum = do_searchpair(&start, &middle, &end, &flags).map_or(0, |(l, _)| l);
*rettv = typval_T::from(lnum);
}
pub fn f_searchpairpos(argvars: &[typval_T], rettv: &mut typval_T) {
let start = tv_get_string(&argvars[0]);
let middle = tv_get_string(&argvars[1]);
let end = tv_get_string(&argvars[2]);
let flags = argvars.get(3).map(tv_get_string).unwrap_or_default();
let (lnum, col) = do_searchpair(&start, &middle, &end, &flags).unwrap_or((0, 0));
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, lnum);
tv_list_append_number(&mut lb, col);
}
pub fn f_searchdecl(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(1 as varnumber_T);
}
pub fn f_getcharsearch(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "char", "");
tv_dict_add_nr(&mut db, "forward", 1);
tv_dict_add_nr(&mut db, "until", 0);
}
fn get_user_input(argvars: &[typval_T], rettv: &mut typval_T, dialog: bool, _secret: bool) {
use std::io::Write;
let prompt = tv_get_string(&argvars[0]);
let default = if argvars.len() > 1 {
tv_get_string(&argvars[1])
} else {
String::new()
};
print!("{prompt}");
let _ = std::io::stdout().flush();
let mut line = String::new();
match std::io::stdin().read_line(&mut line) {
Ok(0) => {
let cancel = if dialog && argvars.len() > 2 {
tv_get_string(&argvars[2])
} else {
default
};
*rettv = typval_T::from(cancel);
return;
}
Ok(_) => {}
Err(_) => {
*rettv = typval_T::from(default);
return;
}
}
while line.ends_with('\n') || line.ends_with('\r') {
line.pop();
}
if line.is_empty() && !default.is_empty() {
*rettv = typval_T::from(default);
} else {
*rettv = typval_T::from(line);
}
}
pub fn f_input(argvars: &[typval_T], rettv: &mut typval_T) {
get_user_input(argvars, rettv, false, false);
}
pub fn f_inputsecret(argvars: &[typval_T], rettv: &mut typval_T) {
get_user_input(argvars, rettv, false, true);
}
pub fn f_inputdialog(argvars: &[typval_T], rettv: &mut typval_T) {
get_user_input(argvars, rettv, true, false);
}
pub fn f_inputsave(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_inputrestore(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_inputlist(argvars: &[typval_T], rettv: &mut typval_T) {
use std::io::Write;
if argvars[0].v_type != VAR_LIST {
emsg("E686: Argument of inputlist() must be a List");
return;
}
if let v_list(Some(l)) = &argvars[0].vval {
for it in &l.borrow().lv_items {
println!("{}", tv_get_string(&it.li_tv));
}
}
let _ = std::io::stdout().flush();
let mut line = String::new();
let n = match std::io::stdin().read_line(&mut line) {
Ok(n) if n > 0 => line.trim().parse::<varnumber_T>().unwrap_or(0),
_ => 0,
};
*rettv = typval_T::from(n);
}
pub fn f_confirm(argvars: &[typval_T], rettv: &mut typval_T) {
use std::io::Write;
let message = tv_get_string(&argvars[0]);
let buttons = if argvars.len() > 1 && argvars[1].v_type != VAR_UNKNOWN {
tv_get_string(&argvars[1])
} else {
"&OK".to_string()
};
let def = if argvars.len() > 2 && argvars[2].v_type != VAR_UNKNOWN {
tv_get_number(&argvars[2])
} else {
1
};
println!("{message}");
for (i, b) in buttons.split('\n').enumerate() {
let label: String = b.chars().filter(|&c| c != '&').collect();
println!("{}) {}", i + 1, label);
}
print!("Type number and <Enter> (default {def}): ");
let _ = std::io::stdout().flush();
let mut line = String::new();
match std::io::stdin().read_line(&mut line) {
Ok(0) | Err(_) => *rettv = typval_T::from(0 as varnumber_T), Ok(_) => {
let t = line.trim();
let choice = if t.is_empty() {
def
} else {
t.parse::<varnumber_T>().unwrap_or(0)
};
*rettv = typval_T::from(choice);
}
}
}
#[derive(Default, Clone)]
pub struct HlGroup {
pub attrs: std::collections::HashMap<String, String>,
pub link: Option<String>,
pub cleared: bool,
}
#[derive(Default)]
pub struct HlRegistry {
order: Vec<String>,
groups: std::collections::HashMap<String, HlGroup>,
}
thread_local! {
pub static HL_GROUPS: std::cell::RefCell<HlRegistry> =
std::cell::RefCell::new(HlRegistry::default());
}
fn hl_intern(reg: &mut HlRegistry, name: &str) -> varnumber_T {
let key = name.to_ascii_lowercase();
if let Some(pos) = reg.order.iter().position(|g| *g == key) {
return (pos + 1) as varnumber_T;
}
reg.order.push(key.clone());
reg.groups.entry(key).or_default();
reg.order.len() as varnumber_T
}
pub fn hl_define_from_args(args: &str) -> Option<String> {
let args = args.trim();
if args.is_empty() {
return None; }
let mut toks = args.split_whitespace().peekable();
let mut first = *toks.peek()?;
if first == "default" {
toks.next();
first = *toks.peek()?;
}
if first == "clear" {
toks.next();
return HL_GROUPS.with(|r| {
let mut reg = r.borrow_mut();
match toks.next() {
Some(g) => {
hl_intern(&mut reg, g);
let key = g.to_ascii_lowercase();
if let Some(def) = reg.groups.get_mut(&key) {
*def = HlGroup {
cleared: true,
..HlGroup::default()
};
}
Some(g.to_string())
}
None => {
for def in reg.groups.values_mut() {
*def = HlGroup {
cleared: true,
..HlGroup::default()
};
}
None
}
}
});
}
if first == "link" {
toks.next();
let from = toks.next()?;
let to = toks.next()?;
return HL_GROUPS.with(|r| {
let mut reg = r.borrow_mut();
hl_intern(&mut reg, from);
hl_intern(&mut reg, to);
let key = from.to_ascii_lowercase();
let def = reg.groups.entry(key).or_default();
def.link = Some(to.to_string());
def.cleared = false;
Some(from.to_string())
});
}
let group = toks.next()?.to_string();
HL_GROUPS.with(|r| {
let mut reg = r.borrow_mut();
hl_intern(&mut reg, &group);
let key = group.to_ascii_lowercase();
let def = reg.groups.entry(key).or_default();
def.cleared = false;
def.link = None;
for kv in toks {
if let Some((k, v)) = kv.split_once('=') {
def.attrs.insert(k.to_ascii_lowercase(), v.to_string());
}
}
Some(group)
})
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct ResolvedHl {
pub guifg: Option<String>,
pub guibg: Option<String>,
pub guisp: Option<String>,
pub ctermfg: Option<String>,
pub ctermbg: Option<String>,
pub attrs: Vec<String>,
pub cleared: bool,
}
pub fn hl_names() -> Vec<String> {
HL_GROUPS.with(|r| r.borrow().order.clone())
}
pub fn hl_resolved(name: &str) -> Option<ResolvedHl> {
HL_GROUPS.with(|r| {
let reg = r.borrow();
let def = reg.groups.get(&name.to_ascii_lowercase())?;
if def.cleared {
return Some(ResolvedHl {
cleared: true,
..ResolvedHl::default()
});
}
let attr_list = hl_lookup(®, name, "gui", 0)
.or_else(|| hl_lookup(®, name, "cterm", 0))
.or_else(|| hl_lookup(®, name, "term", 0))
.map(|list| {
list.split(',')
.filter(|a| !a.eq_ignore_ascii_case("none") && !a.is_empty())
.map(|a| a.to_ascii_lowercase())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let colour =
|key: &str| hl_lookup(®, name, key, 0).filter(|v| !v.eq_ignore_ascii_case("none"));
Some(ResolvedHl {
guifg: colour("guifg"),
guibg: colour("guibg"),
guisp: colour("guisp"),
ctermfg: colour("ctermfg"),
ctermbg: colour("ctermbg"),
attrs: attr_list,
cleared: false,
})
})
}
fn hl_lookup(reg: &HlRegistry, name: &str, key: &str, depth: u8) -> Option<String> {
if depth > 16 {
return None;
}
let def = reg.groups.get(&name.to_ascii_lowercase())?;
if let Some(v) = def.attrs.get(key) {
return Some(v.clone());
}
if let Some(link) = &def.link {
return hl_lookup(reg, link, key, depth + 1);
}
None
}
pub fn hl_id(name: &str) -> varnumber_T {
HL_GROUPS.with(|r| {
let reg = r.borrow();
reg.order
.iter()
.position(|g| *g == name.to_ascii_lowercase())
.map(|p| (p + 1) as varnumber_T)
.unwrap_or(0)
})
}
pub fn hl_exists(name: &str) -> bool {
HL_GROUPS.with(|r| {
r.borrow()
.groups
.get(&name.to_ascii_lowercase())
.is_some_and(|d| !d.cleared)
})
}
fn hl_synidattr(id: varnumber_T, what: &str, mode: &str) -> String {
if id < 1 {
return String::new();
}
HL_GROUPS.with(|r| {
let reg = r.borrow();
let Some(name) = reg.order.get((id - 1) as usize).cloned() else {
return String::new();
};
let gui = mode == "gui" || mode.is_empty();
let what = what.trim_end_matches('#');
let colour = |g_key: &str, c_key: &str| -> String {
let primary = if gui { g_key } else { c_key };
hl_lookup(®, &name, primary, 0)
.or_else(|| hl_lookup(®, &name, if gui { c_key } else { g_key }, 0))
.unwrap_or_default()
};
match what {
"name" => name,
"fg" => colour("guifg", "ctermfg"),
"bg" => colour("guibg", "ctermbg"),
"sp" => colour("guisp", "ctermul"),
"font" => hl_lookup(®, &name, "font", 0).unwrap_or_default(),
"bold" | "italic" | "reverse" | "inverse" | "standout" | "underline" | "undercurl"
| "underdouble" | "underdotted" | "underdashed" | "strikethrough" | "nocombine" => {
let list_key = if gui { "gui" } else { "cterm" };
let hit = hl_lookup(®, &name, list_key, 0)
.or_else(|| hl_lookup(®, &name, "term", 0))
.map(|list| {
let want = if what == "inverse" { "reverse" } else { what };
list.split(',').any(|a| a.eq_ignore_ascii_case(want))
})
.unwrap_or(false);
if hit {
"1".into()
} else {
String::new()
}
}
_ => String::new(),
}
})
}
pub fn f_synID(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_synIDtrans(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_synIDattr(argvars: &[typval_T], rettv: &mut typval_T) {
let id = argvars
.first()
.map(|a| tv_get_number_chk(a, None))
.unwrap_or(0);
let what = argvars.get(1).map(tv_get_string).unwrap_or_default();
let mode = argvars.get(2).map(tv_get_string).unwrap_or_default();
*rettv = typval_T::from(hl_synidattr(id, &what, &mode));
}
pub fn f_synstack(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_synconcealed(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 3);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 0);
tv_list_append_string(&mut lb, "");
tv_list_append_number(&mut lb, 0);
}
pub fn f_changenr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_swapname(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_swapfilelist(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_spellbadword(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_string(&mut lb, "");
tv_list_append_string(&mut lb, "");
}
pub fn f_spellsuggest(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_getregion(argvars: &[typval_T], rettv: &mut typval_T) {
let pos = |tv: &typval_T| -> (varnumber_T, varnumber_T) {
match (tv.v_type, &tv.vval) {
(VAR_LIST, v_list(Some(l))) => {
let it = &l.borrow().lv_items;
(
it.get(1).map_or(0, |x| tv_get_number(&x.li_tv)),
it.get(2).map_or(0, |x| tv_get_number(&x.li_tv)),
)
}
_ => (0, 0),
}
};
let (mut l1, mut c1) = pos(&argvars[0]);
let (mut l2, mut c2) = pos(&argvars[1]);
if (l1, c1) > (l2, c2) {
std::mem::swap(&mut l1, &mut l2);
std::mem::swap(&mut c1, &mut c2);
}
let linewise = argvars
.get(2)
.and_then(|o| match (o.v_type, &o.vval) {
(VAR_DICT, v_dict(Some(d))) => tv_dict_find(&d.borrow(), "type").map(tv_get_string),
_ => None,
})
.is_some_and(|t| t.starts_with('V'));
let out = tv_list_alloc_ret(rettv, 0);
let mut ob = out.borrow_mut();
let lines = get_buffer_lines(l1, l2);
for (i, line) in lines.iter().enumerate() {
let lnum = l1 + i as varnumber_T;
let piece = if linewise {
line.clone()
} else {
let start = if lnum == l1 {
(c1 as usize).saturating_sub(1)
} else {
0
};
let end = if lnum == l2 {
(c2 as usize).min(line.len())
} else {
line.len()
};
line.get(start.min(line.len())..end.max(start.min(line.len())))
.unwrap_or("")
.to_string()
};
tv_list_append_string(&mut ob, &piece);
}
}
pub fn f_getregionpos(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_matchbufline(argvars: &[typval_T], rettv: &mut typval_T) {
let pat = tv_get_string(&argvars[1]);
let lnum = tv_get_lnum(&argvars[2]);
let end = tv_get_lnum(&argvars[3]);
let ic = tv_get_number_chk(&get_option_value("ignorecase"), None) != 0;
let submatches = argvars.get(4).is_some_and(|d| match &d.vval {
v_dict(Some(dd)) => {
dd.borrow()
.dv_hashtab
.get("submatches")
.map(tv_get_bool)
.unwrap_or(0)
!= 0
}
_ => false,
});
let out = tv_list_alloc_ret(rettv, 0);
for (i, line) in get_buffer_lines(lnum, end).iter().enumerate() {
let ln = lnum + i as varnumber_T;
get_matches_in_str(line, &pat, ic, &out, ln, submatches, true);
}
}
pub fn f_menu_get(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_timer_info(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_timer_start(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_timer_stop(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_timer_pause(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_timer_stopall(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_setpos(argvars: &[typval_T], rettv: &mut typval_T) {
let expr = tv_get_string(&argvars[0]);
if let (VAR_LIST, v_list(Some(l))) = (argvars[1].v_type, &argvars[1].vval) {
let items = &l.borrow().lv_items;
let lnum = items.get(1).map_or(0, |it| tv_get_number(&it.li_tv));
let col = items.get(2).map_or(1, |it| tv_get_number(&it.li_tv));
if expr == "." {
set_cursorpos(lnum, col);
} else if let Some(name) = expr.strip_prefix('\'').and_then(|r| r.chars().next()) {
setmark(name, lnum, col);
}
}
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_setcharpos(argvars: &[typval_T], rettv: &mut typval_T) {
f_setpos(argvars, rettv);
}
pub fn f_cursor(argvars: &[typval_T], rettv: &mut typval_T) {
let (lnum, col) = if argvars[0].v_type == VAR_LIST {
if let (VAR_LIST, v_list(Some(l))) = (argvars[0].v_type, &argvars[0].vval) {
let items = &l.borrow().lv_items;
(
items.first().map_or(1, |it| tv_get_number(&it.li_tv)),
items.get(1).map_or(1, |it| tv_get_number(&it.li_tv)),
)
} else {
(1, 1)
}
} else {
(
tv_get_number(&argvars[0]),
argvars.get(1).map_or(1, tv_get_number),
)
};
set_cursorpos(lnum, col);
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_setcursorcharpos(argvars: &[typval_T], rettv: &mut typval_T) {
f_cursor(argvars, rettv);
}
pub fn f_setcharsearch(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_settagstack(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
#[derive(Clone, Copy, PartialEq)]
enum AssertType {
Equal,
NotEqual,
Match,
NotMatch,
Other,
}
fn fill_assert_error(
opt_msg: Option<&typval_T>,
exp_str: Option<&str>,
exp_tv: &typval_T,
got_tv: &typval_T,
atype: AssertType,
) -> String {
let mut s = String::new();
if let Some(m) = opt_msg {
if m.v_type != VAR_UNKNOWN {
s.push_str(&encode_tv2echo(m));
s.push_str(": ");
}
}
s.push_str(match atype {
AssertType::Match | AssertType::NotMatch => "Pattern ",
AssertType::NotEqual => "Expected not equal to ",
_ => "Expected ",
});
match exp_str {
Some(e) => s.push_str(e),
None => s.push_str(&encode_tv2string(exp_tv)),
}
match atype {
AssertType::NotEqual => {}
AssertType::Match => {
s.push_str(" does not match ");
s.push_str(&encode_tv2string(got_tv));
}
AssertType::NotMatch => {
s.push_str(" does match ");
s.push_str(&encode_tv2string(got_tv));
}
_ => {
s.push_str(" but got ");
s.push_str(&encode_tv2string(got_tv));
}
}
s
}
fn assert_equal_common(argvars: &[typval_T], rettv: &mut typval_T, want_equal: bool) {
let equal = tv_equal(&argvars[0], &argvars[1], false);
if equal != want_equal {
let atype = if want_equal {
AssertType::Equal
} else {
AssertType::NotEqual
};
let msg = fill_assert_error(argvars.get(2), None, &argvars[0], &argvars[1], atype);
assert_error(&msg);
*rettv = typval_T::from(1 as varnumber_T);
} else {
*rettv = typval_T::from(0 as varnumber_T);
}
}
pub fn f_assert_equal(argvars: &[typval_T], rettv: &mut typval_T) {
assert_equal_common(argvars, rettv, true);
}
pub fn f_assert_notequal(argvars: &[typval_T], rettv: &mut typval_T) {
assert_equal_common(argvars, rettv, false);
}
fn assert_bool(argvars: &[typval_T], rettv: &mut typval_T, is_true: bool) {
let v = &argvars[0];
let ok = match v.v_type {
VAR_NUMBER => (tv_get_number(v) != 0) == is_true,
VAR_BOOL => (tv_get_bool(v) != 0) == is_true,
_ => false,
};
if !ok {
let lit = if is_true { "True" } else { "False" };
let msg = fill_assert_error(argvars.get(1), Some(lit), v, v, AssertType::Other);
assert_error(&msg);
*rettv = typval_T::from(1 as varnumber_T);
} else {
*rettv = typval_T::from(0 as varnumber_T);
}
}
pub fn f_assert_true(argvars: &[typval_T], rettv: &mut typval_T) {
assert_bool(argvars, rettv, true);
}
pub fn f_assert_false(argvars: &[typval_T], rettv: &mut typval_T) {
assert_bool(argvars, rettv, false);
}
fn assert_match_common(argvars: &[typval_T], rettv: &mut typval_T, want_match: bool) {
let pat = tv_get_string(&argvars[0]);
let actual = tv_get_string(&argvars[1]);
let matched = regex_match(&pat, &actual, false);
if matched != want_match {
let atype = if want_match {
AssertType::Match
} else {
AssertType::NotMatch
};
let msg = fill_assert_error(argvars.get(2), None, &argvars[0], &argvars[1], atype);
assert_error(&msg);
*rettv = typval_T::from(1 as varnumber_T);
} else {
*rettv = typval_T::from(0 as varnumber_T);
}
}
pub fn f_assert_match(argvars: &[typval_T], rettv: &mut typval_T) {
assert_match_common(argvars, rettv, true);
}
pub fn f_assert_notmatch(argvars: &[typval_T], rettv: &mut typval_T) {
assert_match_common(argvars, rettv, false);
}
pub fn f_assert_report(argvars: &[typval_T], rettv: &mut typval_T) {
assert_error(&tv_get_string(&argvars[0]));
*rettv = typval_T::from(1 as varnumber_T);
}
pub fn f_assert_inrange(argvars: &[typval_T], rettv: &mut typval_T) {
let as_f64 = |tv: &typval_T| -> f64 {
if tv.v_type == VAR_FLOAT {
tv_get_float(tv)
} else {
tv_get_number(tv) as f64
}
};
let lower = as_f64(&argvars[0]);
let upper = as_f64(&argvars[1]);
let actual = as_f64(&argvars[2]);
if actual < lower || actual > upper {
let mut msg = String::new();
if let Some(m) = argvars.get(3) {
if m.v_type != VAR_UNKNOWN {
msg.push_str(&encode_tv2echo(m));
msg.push_str(": ");
}
}
msg.push_str(&format!(
"Expected range {} - {}, but got {}",
encode_tv2string(&argvars[0]),
encode_tv2string(&argvars[1]),
encode_tv2string(&argvars[2]),
));
assert_error(&msg);
*rettv = typval_T::from(1 as varnumber_T);
} else {
*rettv = typval_T::from(0 as varnumber_T);
}
}
pub fn f_assert_exception(argvars: &[typval_T], rettv: &mut typval_T) {
let error = tv_get_string(&argvars[0]);
let exc = get_vim_var_str(VV_EXCEPTION);
if exc.is_empty() {
assert_error("v:exception is not set");
*rettv = typval_T::from(1 as varnumber_T);
} else if !exc.contains(&error) {
let got = typval_T::from(exc);
let msg = fill_assert_error(argvars.get(1), None, &argvars[0], &got, AssertType::Other);
assert_error(&msg);
*rettv = typval_T::from(1 as varnumber_T);
} else {
*rettv = typval_T::from(0 as varnumber_T);
}
}
fn get_cmd_output(argvars: &[typval_T]) -> Vec<u8> {
use std::io::Write;
use std::process::{Command, Stdio};
let cmd = tv_get_string(&argvars[0]);
let input = if argvars.len() > 1 && argvars[1].v_type != VAR_UNKNOWN {
Some(tv_get_string(&argvars[1]))
} else {
None
};
let mut command = Command::new("sh");
command.arg("-c").arg(&cmd).stdout(Stdio::piped());
command.stdin(if input.is_some() {
Stdio::piped()
} else {
Stdio::null()
});
let mut child = match command.spawn() {
Ok(c) => c,
Err(_) => {
set_vim_var_nr(VV_SHELL_ERROR, -1);
return Vec::new();
}
};
if let Some(text) = input {
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(text.as_bytes());
}
}
match child.wait_with_output() {
Ok(out) => {
set_vim_var_nr(
VV_SHELL_ERROR,
out.status.code().unwrap_or(-1) as varnumber_T,
);
out.stdout
}
Err(_) => {
set_vim_var_nr(VV_SHELL_ERROR, -1);
Vec::new()
}
}
}
pub fn f_system(argvars: &[typval_T], rettv: &mut typval_T) {
let out = get_cmd_output(argvars);
*rettv = typval_T::from(String::from_utf8_lossy(&out).into_owned());
}
pub fn f_systemlist(argvars: &[typval_T], rettv: &mut typval_T) {
let out = String::from_utf8_lossy(&get_cmd_output(argvars)).into_owned();
let l = tv_list_alloc_ret(rettv, 0);
let mut lb = l.borrow_mut();
let trimmed = out.strip_suffix('\n').unwrap_or(&out);
if !trimmed.is_empty() || out.contains('\n') {
for line in trimmed.split('\n') {
tv_list_append_string(&mut lb, line);
}
}
}
pub fn f_environ(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
for (k, v) in std::env::vars_os() {
tv_dict_add_str(&mut db, &k.to_string_lossy(), &v.to_string_lossy());
}
}
pub fn f_bufnr(_argvars: &[typval_T], rettv: &mut typval_T) {
let n = crate::fusevm_bridge::editor_buf_nr().unwrap_or(-1);
*rettv = typval_T::from(n);
}
pub fn f_bufexists(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_buflisted(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_bufloaded(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_bufname(_argvars: &[typval_T], rettv: &mut typval_T) {
let name = crate::fusevm_bridge::editor_buf_name().unwrap_or_default();
*rettv = typval_T::from(name);
}
pub fn f_bufwinnr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_bufwinid(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_winnr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(1 as varnumber_T);
}
pub fn f_winbufnr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_winwidth(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_winheight(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_winlayout(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_winline(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_wincol(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_winrestcmd(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_tabpagenr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(1 as varnumber_T);
}
pub fn f_tabpagewinnr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(1 as varnumber_T);
}
pub fn f_getline(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[0]);
if argvars.len() >= 2 && argvars[1].v_type != VAR_UNKNOWN {
let end = tv_get_lnum(&argvars[1]);
let lines = get_buffer_lines(lnum, end);
let l = tv_list_alloc_ret(rettv, lines.len() as isize);
let mut lb = l.borrow_mut();
for line in lines {
tv_list_append_string(&mut lb, &line);
}
} else {
let s = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
*rettv = typval_T::from(s);
}
}
pub fn f_getbufline(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[1]);
let end = if argvars.len() >= 3 && argvars[2].v_type != VAR_UNKNOWN {
tv_get_lnum(&argvars[2])
} else {
lnum
};
let lines = get_buffer_lines(lnum, end);
let l = tv_list_alloc_ret(rettv, lines.len() as isize);
let mut lb = l.borrow_mut();
for line in lines {
tv_list_append_string(&mut lb, &line);
}
}
pub fn f_getbufoneline(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[1]);
let s = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
*rettv = typval_T::from(s);
}
pub fn f_getbufinfo(_argvars: &[typval_T], rettv: &mut typval_T) {
let out = tv_list_alloc_ret(rettv, 1);
let mut ob = out.borrow_mut();
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "bufnr", 1);
tv_dict_add_str(&mut db, "name", "");
tv_dict_add_nr(
&mut db,
"lnum",
crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow())).0,
);
tv_dict_add_nr(&mut db, "linecount", curbuf_len());
tv_dict_add_nr(&mut db, "loaded", 1);
tv_dict_add_nr(&mut db, "listed", 1);
tv_dict_add_nr(&mut db, "hidden", 0);
tv_dict_add_nr(&mut db, "changed", 0);
tv_dict_add_nr(&mut db, "changedtick", 1);
let empty_list = |db: &mut crate::ported::eval::typval_defs_h::dict_T, k: &str| {
let l = tv_list_alloc(0);
tv_dict_add_tv(
db,
k,
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
},
);
};
empty_list(&mut db, "windows");
empty_list(&mut db, "popups");
}
tv_list_append_tv(&mut ob, match_dict_val(d));
}
pub fn f_setline(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[0]);
let r = set_buffer_lines(lnum, tv_lines_arg(&argvars[1]), false);
*rettv = typval_T::from(r);
}
pub fn f_setbufline(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[1]);
let r = set_buffer_lines(lnum, tv_lines_arg(&argvars[2]), false);
*rettv = typval_T::from(r);
}
pub fn f_append(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[0]);
let r = set_buffer_lines(lnum, tv_lines_arg(&argvars[1]), true);
*rettv = typval_T::from(r);
}
pub fn f_appendbufline(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[1]);
let r = set_buffer_lines(lnum, tv_lines_arg(&argvars[2]), true);
*rettv = typval_T::from(r);
}
pub fn f_deletebufline(argvars: &[typval_T], rettv: &mut typval_T) {
let first = tv_get_lnum(&argvars[1]);
let last = if argvars.len() >= 3 && argvars[2].v_type != VAR_UNKNOWN {
tv_get_lnum(&argvars[2])
} else {
first
};
let len = curbuf_len();
if first < 1 || first > len || last < first {
*rettv = typval_T::from(1 as varnumber_T);
return;
}
CURBUF.with(|b| {
let mut b = b.borrow_mut();
if b.is_empty() {
b.push(String::new());
}
let lo = (first - 1) as usize;
let hi = (last.min(b.len() as varnumber_T)) as usize;
b.drain(lo..hi);
if b.is_empty() {
b.push(String::new());
}
});
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_getwininfo(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_gettabinfo(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_getwinpos(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, -1);
tv_list_append_number(&mut lb, -1);
}
pub fn f_getwinposx(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_getwinposy(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_win_getid(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_win_id2win(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_win_findbuf(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_win_gotoid(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_win_gettype(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from("unknown".to_string());
}
pub fn f_win_screenpos(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 0);
tv_list_append_number(&mut lb, 0);
}
pub fn f_win_id2tabwin(_argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 2);
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 0);
tv_list_append_number(&mut lb, 0);
}
pub fn f_win_splitmove(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_win_move_separator(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_win_move_statusline(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_getcmdwintype(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_winrestview(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_winsaveview(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "lnum", 1);
tv_dict_add_nr(&mut db, "col", 0);
tv_dict_add_nr(&mut db, "coladd", 0);
tv_dict_add_nr(&mut db, "curswant", 0);
tv_dict_add_nr(&mut db, "topline", 1);
tv_dict_add_nr(&mut db, "topfill", 0);
tv_dict_add_nr(&mut db, "leftcol", 0);
tv_dict_add_nr(&mut db, "skipcol", 0);
}
pub fn f_bufload(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_prompt_getinput(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_prompt_setprompt(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_prompt_setcallback(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_prompt_setinterrupt(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_interrupt(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_debugbreak(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_api_info(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_dict_alloc_ret(rettv);
}
pub fn f_swapinfo(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
tv_dict_add_str(&mut d.borrow_mut(), "error", "Cannot open file");
}
pub fn f_serverstart(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_serverstop(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
fn get_var_from(argvars: &[typval_T], idx: usize) -> typval_T {
match argvars.get(idx) {
Some(d) if d.v_type != VAR_UNKNOWN => d.clone(),
_ => typval_T::from(String::new()),
}
}
pub fn f_getbufvar(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = get_var_from(argvars, 2);
}
pub fn f_getwinvar(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = get_var_from(argvars, 2);
}
pub fn f_gettabvar(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = get_var_from(argvars, 2);
}
pub fn f_gettabwinvar(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = get_var_from(argvars, 3);
}
pub fn f_setbufvar(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_setwinvar(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_settabvar(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_settabwinvar(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_jobstart(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_jobpid(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_jobstop(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_jobwait(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_list_alloc_ret(rettv, 0);
}
pub fn f_jobresize(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_chanclose(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_chansend(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_feedkeys(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_wait(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn f_sockconnect(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_win_execute(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_bufadd(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_ctxget(_argvars: &[typval_T], rettv: &mut typval_T) {
tv_dict_alloc_ret(rettv);
}
pub fn f_ctxpop(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_ctxpush(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_ctxset(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_ctxsize(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_islocked(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_last_buffer_nr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_libcall(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(String::new());
}
pub fn f_libcallnr(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
fn mpack_encode_tv(tv: &typval_T, out: &mut Vec<u8>) -> Result<(), &'static str> {
match (tv.v_type, &tv.vval) {
(VAR_SPECIAL, _) => out.push(0xc0),
(VAR_BOOL, v_bool(b)) => out.push(if *b == kBoolVarTrue { 0xc3 } else { 0xc2 }),
(VAR_NUMBER, v_number(n)) => mpack_pack_int(*n, out),
(VAR_FLOAT, v_float(f)) => {
out.push(0xcb);
out.extend_from_slice(&f.to_bits().to_be_bytes());
}
(VAR_STRING, v_string(s)) => mpack_pack_bin(s.as_bytes(), out),
(VAR_BLOB, v_blob(b)) => {
let bytes = b
.as_ref()
.map(|b| b.borrow().bv_ga.clone())
.unwrap_or_default();
mpack_pack_bin(&bytes, out);
}
(VAR_LIST, v_list(l)) => {
let items: Vec<typval_T> = l
.as_ref()
.map(|l| {
l.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect()
})
.unwrap_or_default();
mpack_pack_array_len(items.len(), out);
for it in &items {
mpack_encode_tv(it, out)?;
}
}
(VAR_DICT, v_dict(d)) => {
let pairs: Vec<(String, typval_T)> = d
.as_ref()
.map(|d| {
d.borrow()
.dv_hashtab
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
})
.unwrap_or_default();
mpack_pack_map_len(pairs.len(), out);
for (k, v) in &pairs {
mpack_pack_str(k.as_bytes(), out);
mpack_encode_tv(v, out)?;
}
}
(VAR_FUNC, _) | (VAR_PARTIAL, _) => {
return Err("E5004: Error while dumping: attempt to dump function reference")
}
_ => return Err("E5004: Error while dumping: attempt to dump unsupported type"),
}
Ok(())
}
fn mpack_pack_int(n: varnumber_T, out: &mut Vec<u8>) {
if n >= 0 {
let u = n as u64;
if u < 0x80 {
out.push(u as u8); } else if u <= 0xff {
out.push(0xcc);
out.push(u as u8);
} else if u <= 0xffff {
out.push(0xcd);
out.extend_from_slice(&(u as u16).to_be_bytes());
} else if u <= 0xffff_ffff {
out.push(0xce);
out.extend_from_slice(&(u as u32).to_be_bytes());
} else {
out.push(0xcf);
out.extend_from_slice(&u.to_be_bytes());
}
} else if n >= -32 {
out.push((n as i8) as u8); } else if n >= -128 {
out.push(0xd0);
out.push((n as i8) as u8);
} else if n >= -32768 {
out.push(0xd1);
out.extend_from_slice(&(n as i16).to_be_bytes());
} else if n >= -(1i64 << 31) {
out.push(0xd2);
out.extend_from_slice(&(n as i32).to_be_bytes());
} else {
out.push(0xd3);
out.extend_from_slice(&n.to_be_bytes());
}
}
fn mpack_pack_str(b: &[u8], out: &mut Vec<u8>) {
let n = b.len();
if n < 32 {
out.push(0xa0 | n as u8);
} else if n <= 0xff {
out.push(0xd9);
out.push(n as u8);
} else if n <= 0xffff {
out.push(0xda);
out.extend_from_slice(&(n as u16).to_be_bytes());
} else {
out.push(0xdb);
out.extend_from_slice(&(n as u32).to_be_bytes());
}
out.extend_from_slice(b);
}
fn mpack_pack_bin(b: &[u8], out: &mut Vec<u8>) {
let n = b.len();
if n <= 0xff {
out.push(0xc4);
out.push(n as u8);
} else if n <= 0xffff {
out.push(0xc5);
out.extend_from_slice(&(n as u16).to_be_bytes());
} else {
out.push(0xc6);
out.extend_from_slice(&(n as u32).to_be_bytes());
}
out.extend_from_slice(b);
}
fn mpack_pack_array_len(n: usize, out: &mut Vec<u8>) {
if n < 16 {
out.push(0x90 | n as u8);
} else if n <= 0xffff {
out.push(0xdc);
out.extend_from_slice(&(n as u16).to_be_bytes());
} else {
out.push(0xdd);
out.extend_from_slice(&(n as u32).to_be_bytes());
}
}
fn mpack_pack_map_len(n: usize, out: &mut Vec<u8>) {
if n < 16 {
out.push(0x80 | n as u8);
} else if n <= 0xffff {
out.push(0xde);
out.extend_from_slice(&(n as u16).to_be_bytes());
} else {
out.push(0xdf);
out.extend_from_slice(&(n as u32).to_be_bytes());
}
}
fn mpack_input_bytes(tv: &typval_T) -> Result<Vec<u8>, &'static str> {
match (tv.v_type, &tv.vval) {
(VAR_BLOB, v_blob(b)) => Ok(b
.as_ref()
.map(|b| b.borrow().bv_ga.clone())
.unwrap_or_default()),
(VAR_LIST, v_list(l)) => {
let items: Vec<String> = l
.as_ref()
.map(|l| {
l.borrow()
.lv_items
.iter()
.map(|it| tv_get_string(&it.li_tv))
.collect()
})
.unwrap_or_default();
Ok(items.join("\n").into_bytes())
}
_ => Err("E5070: msgpackparse() argument must be a List or Blob"),
}
}
pub fn f_msgpackdump(argvars: &[typval_T], rettv: &mut typval_T) {
let items: Vec<typval_T> = match &argvars[0].vval {
v_list(Some(l)) => l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect(),
_ => {
emsg("E1211: List required for argument 1");
tv_list_alloc_ret(rettv, 0);
return;
}
};
let want_blob = argvars.len() > 1 && tv_get_string(&argvars[1]).contains('B');
let mut bytes = Vec::new();
for it in &items {
if let Err(e) = mpack_encode_tv(it, &mut bytes) {
emsg(e);
tv_list_alloc_ret(rettv, 0);
return;
}
}
if want_blob {
let blob = tv_blob_alloc_ret(rettv);
blob.borrow_mut().bv_ga = bytes;
return;
}
let l = tv_list_alloc_ret(rettv, 0);
if bytes.is_empty() {
return;
}
let text = String::from_utf8_lossy(&bytes);
let mut lb = l.borrow_mut();
for line in text.split('\n') {
tv_list_append_string(&mut lb, line);
}
}
pub fn f_msgpackparse(argvars: &[typval_T], rettv: &mut typval_T) {
let bytes = match mpack_input_bytes(&argvars[0]) {
Ok(b) => b,
Err(e) => {
emsg(e);
tv_list_alloc_ret(rettv, 0);
return;
}
};
let l = tv_list_alloc_ret(rettv, 0);
let mut data: &[u8] = &bytes;
let mut size = bytes.len();
while size > 0 {
let mut item = typval_T::default();
if crate::ported::eval::decode::unpack_typval(&mut data, &mut size, &mut item)
!= crate::ported::mpack::MPACK_OK
{
emsg("E5766: failed to parse msgpack string");
break;
}
tv_list_append_tv(&mut l.borrow_mut(), item);
}
}
pub fn f_rpcnotify(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_rpcrequest(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_rpcstart(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_rpcstop(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_stdioopen(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_submatch(argvars: &[typval_T], rettv: &mut typval_T) {
let no = tv_get_number_chk(&argvars[0], None).max(0) as usize;
if argvars.len() >= 2 && argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1]) != 0 {
let l = tv_list_alloc_ret(rettv, 0);
if crate::viml_regex::has_submatch_context() {
let mut lb = l.borrow_mut();
for line in crate::viml_regex::current_submatch(no).split('\n') {
tv_list_append_string(&mut lb, line);
}
}
} else {
*rettv = typval_T::from(crate::viml_regex::current_submatch(no));
}
}
pub fn f_prompt_appendbuf(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(0 as varnumber_T);
}
pub fn f_py3eval(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
pub fn f_perleval(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
fn get_appname() -> String {
std::env::var("NVIM_APPNAME")
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "nvim".to_string())
}
fn get_xdg_var_list(env: &str, default: &str) -> Vec<String> {
let appname = get_appname();
let val = std::env::var(env)
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| default.to_string());
val.split(':')
.filter(|s| !s.is_empty())
.map(|d| format!("{d}/{appname}"))
.collect()
}
pub fn f_stdpath(argvars: &[typval_T], rettv: &mut typval_T) {
let home = std::env::var("HOME").unwrap_or_default();
let appname = get_appname();
let xdg_home = |env: &str, default_rel: &str| -> String {
let base = std::env::var(env)
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| format!("{home}/{default_rel}"));
format!("{base}/{appname}")
};
let kind = tv_get_string(&argvars[0]);
match kind.as_str() {
"config" => *rettv = typval_T::from(xdg_home("XDG_CONFIG_HOME", ".config")),
"data" => *rettv = typval_T::from(xdg_home("XDG_DATA_HOME", ".local/share")),
"cache" => *rettv = typval_T::from(xdg_home("XDG_CACHE_HOME", ".cache")),
"state" => *rettv = typval_T::from(xdg_home("XDG_STATE_HOME", ".local/state")),
"log" => {
*rettv = typval_T::from(format!(
"{}/logs",
xdg_home("XDG_STATE_HOME", ".local/state")
))
}
"run" => {
let run = std::env::var("XDG_RUNTIME_DIR").unwrap_or_default();
*rettv = typval_T::from(format!("{run}/{appname}"));
}
"config_dirs" | "data_dirs" => {
let dirs = if kind == "config_dirs" {
get_xdg_var_list("XDG_CONFIG_DIRS", "/etc/xdg")
} else {
get_xdg_var_list("XDG_DATA_DIRS", "/usr/local/share:/usr/share")
};
let l = tv_list_alloc_ret(rettv, dirs.len() as isize);
let mut lb = l.borrow_mut();
for d in &dirs {
tv_list_append_string(&mut lb, d);
}
}
_ => {
emsg(&format!("E6100: \"{kind}\" is not a valid stdpath"));
*rettv = typval_T::from(String::new());
}
}
}
pub fn f_keytrans(argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(tv_get_string(&argvars[0]));
}
pub fn f_luaeval(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
pub fn f_rubyeval(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
pub fn f_termopen(_argvars: &[typval_T], rettv: &mut typval_T) {
*rettv = typval_T::from(-1 as varnumber_T);
}
pub fn has_wsl() -> bool {
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.map(|s| s.to_lowercase().contains("microsoft"))
.unwrap_or(false)
}
pub fn emsg_mpack_error(status: i32) {
if status != 0 {
emsg("E5004: Error while dumping or parsing msgpack");
}
}
pub fn find_win_for_curbuf() {}
pub fn buf_win_common(_argvars: &[typval_T], rettv: &mut typval_T, _get_nr: bool) {
*rettv = typval_T::from(-1 as varnumber_T);
}
use crate::ported::eval::typval_defs_h::VarLockStatus;
const FUZZY_SEQUENTIAL_BONUS: i32 = 15;
const FUZZY_SEPARATOR_BONUS: i32 = 30;
const FUZZY_CAMEL_BONUS: i32 = 30;
const FUZZY_FIRST_LETTER_BONUS: i32 = 15;
const FUZZY_LEADING_LETTER_PENALTY: i32 = -5;
const FUZZY_MAX_LEADING_LETTER_PENALTY: i32 = -15;
const FUZZY_UNMATCHED_LETTER_PENALTY: i32 = -1;
const FUZZY_RECURSION_LIMIT: i32 = 10;
const FUZZY_MAX_MATCHES: usize = 256;
fn fuzzy_match_compute_score(str: &[char], matches: &[usize], camelcase: bool) -> i32 {
let mut score = 100;
let mut penalty = FUZZY_LEADING_LETTER_PENALTY * matches[0] as i32;
if penalty < FUZZY_MAX_LEADING_LETTER_PENALTY {
penalty = FUZZY_MAX_LEADING_LETTER_PENALTY;
}
score += penalty;
let unmatched = str.len() as i32 - matches.len() as i32;
score += FUZZY_UNMATCHED_LETTER_PENALTY * unmatched;
for i in 0..matches.len() {
let curr_idx = matches[i];
if i > 0 && curr_idx == matches[i - 1] + 1 {
score += FUZZY_SEQUENTIAL_BONUS;
}
if curr_idx > 0 {
let neighbor = str[curr_idx - 1];
let curr = str[curr_idx];
if camelcase && neighbor.is_lowercase() && curr.is_uppercase() {
score += FUZZY_CAMEL_BONUS;
}
if neighbor == '/' || neighbor == '\\' || neighbor == ' ' || neighbor == '_' {
score += FUZZY_SEPARATOR_BONUS;
}
} else {
score += FUZZY_FIRST_LETTER_BONUS;
}
}
score
}
#[allow(clippy::too_many_arguments)]
fn fuzzy_match_recursive(
fuzpat: &[char],
str_rem: &[char],
str_idx: usize,
str_full: &[char],
camelcase: bool,
matches: &mut Vec<usize>,
recursion: &mut i32,
) -> Option<i32> {
*recursion += 1;
if *recursion >= FUZZY_RECURSION_LIMIT {
return None;
}
if fuzpat.is_empty() || str_rem.is_empty() {
return None;
}
let mut recursive_best: Option<(i32, Vec<usize>)> = None;
let mut fp = 0usize;
let mut sp = 0usize;
while fp < fuzpat.len() && sp < str_rem.len() {
let c1 = fuzpat[fp];
let c2 = str_rem[sp];
if c1.to_lowercase().eq(c2.to_lowercase()) {
if matches.len() >= FUZZY_MAX_MATCHES {
return None;
}
let mut rec_matches = matches.clone();
if let Some(rscore) = fuzzy_match_recursive(
&fuzpat[fp..],
&str_rem[sp + 1..],
str_idx + sp + 1,
str_full,
camelcase,
&mut rec_matches,
recursion,
) {
#[allow(clippy::unnecessary_map_or)]
if recursive_best.as_ref().map_or(true, |(bs, _)| rscore > *bs) {
recursive_best = Some((rscore, rec_matches));
}
}
matches.push(str_idx + sp);
fp += 1;
}
sp += 1;
}
let matched = fp >= fuzpat.len();
let this_score = if matched {
fuzzy_match_compute_score(str_full, matches, camelcase)
} else {
0
};
if let Some((rscore, rmatches)) = recursive_best {
if !matched || rscore > this_score {
*matches = rmatches;
return Some(rscore);
}
}
if matched {
return Some(this_score);
}
None
}
fn fuzzy_match(
str: &[char],
pat: &str,
matchseq: bool,
camelcase: bool,
) -> Option<(i32, Vec<usize>)> {
let words: Vec<Vec<char>> = if matchseq {
vec![pat.chars().collect()]
} else {
pat.split(' ')
.filter(|w| !w.is_empty())
.map(|w| w.chars().collect())
.collect()
};
if words.is_empty() {
return None;
}
let mut total = 0i32;
let mut all: Vec<usize> = Vec::new();
for w in &words {
let mut matches: Vec<usize> = Vec::new();
let mut recursion = 0i32;
match fuzzy_match_recursive(w, str, 0, str, camelcase, &mut matches, &mut recursion) {
Some(score) => {
total += score;
all.extend_from_slice(&matches);
}
None => return None,
}
}
Some((total, all))
}
fn do_fuzzymatch(argvars: &[typval_T], rettv: &mut typval_T, return_pos: bool) {
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
_ => {
emsg("E714: List required");
return;
}
};
let pat = tv_get_string(&argvars[1]);
let mut key: Option<String> = None;
let mut matchseq = false;
let mut camelcase = true;
let mut limit: i64 = 0;
if argvars.len() >= 3 {
if let (VAR_DICT, v_dict(Some(d))) = (argvars[2].v_type, &argvars[2].vval) {
let d = d.borrow();
if let Some(k) = tv_dict_find(&d, "key") {
key = Some(tv_get_string(k));
}
if let Some(v) = tv_dict_find(&d, "matchseq") {
matchseq = tv_get_number(v) != 0;
}
if let Some(v) = tv_dict_find(&d, "camelcase") {
camelcase = tv_get_bool(v) != 0;
}
if let Some(v) = tv_dict_find(&d, "limit") {
limit = tv_get_number(v);
}
}
}
let items: Vec<typval_T> = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
let mut scored: Vec<(usize, i32, Vec<usize>)> = Vec::new();
for (idx, item) in items.iter().enumerate() {
let text = match &key {
Some(k) => match (item.v_type, &item.vval) {
(VAR_DICT, v_dict(Some(d))) => match tv_dict_find(&d.borrow(), k) {
Some(v) => tv_get_string(v),
None => continue,
},
_ => continue,
},
None => {
if item.v_type == VAR_STRING {
tv_get_string(item)
} else {
continue;
}
}
};
let chars: Vec<char> = text.chars().collect();
if let Some((score, positions)) = fuzzy_match(&chars, &pat, matchseq, camelcase) {
scored.push((idx, score, positions));
}
}
scored.sort_by_key(|x| std::cmp::Reverse(x.1));
if limit > 0 && scored.len() > limit as usize {
scored.truncate(limit as usize);
}
if !return_pos {
let out = tv_list_alloc_ret(rettv, scored.len() as isize);
let mut ob = out.borrow_mut();
for (idx, _, _) in &scored {
tv_list_append_tv(&mut ob, items[*idx].clone());
}
return;
}
let outer = tv_list_alloc_ret(rettv, 3);
let matched = tv_list_alloc(0);
let posl = tv_list_alloc(0);
let scorel = tv_list_alloc(0);
for (idx, score, positions) in &scored {
tv_list_append_tv(&mut matched.borrow_mut(), items[*idx].clone());
let p = tv_list_alloc(0);
for pos in positions {
tv_list_append_number(&mut p.borrow_mut(), *pos as varnumber_T);
}
tv_list_append_tv(
&mut posl.borrow_mut(),
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(p)),
},
);
tv_list_append_number(&mut scorel.borrow_mut(), *score as varnumber_T);
}
let mk = |l| typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
let mut ob = outer.borrow_mut();
tv_list_append_tv(&mut ob, mk(matched));
tv_list_append_tv(&mut ob, mk(posl));
tv_list_append_tv(&mut ob, mk(scorel));
}
pub fn f_matchfuzzy(argvars: &[typval_T], rettv: &mut typval_T) {
do_fuzzymatch(argvars, rettv, false);
}
pub fn f_matchfuzzypos(argvars: &[typval_T], rettv: &mut typval_T) {
do_fuzzymatch(argvars, rettv, true);
}
thread_local! {
static HISTORY: std::cell::RefCell<[Vec<String>; 5]> =
const { std::cell::RefCell::new([Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()]) };
}
fn get_histtype(name: &str) -> Option<usize> {
match name {
"" | ":" | "cmd" => Some(0),
"/" | "?" | "search" => Some(1),
"=" | "expr" => Some(2),
"@" | "input" => Some(3),
">" | "debug" => Some(4),
_ => None,
}
}
pub fn f_histadd(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
let item = tv_get_string(&argvars[1]);
let t = match get_histtype(&name) {
Some(t) => t,
None => return,
};
if item.is_empty() {
return;
}
HISTORY.with(|h| {
let mut h = h.borrow_mut();
h[t].retain(|e| e != &item);
h[t].push(item);
});
rettv.vval = v_number(1);
}
pub fn f_histget(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let name = tv_get_string(&argvars[0]);
let t = match get_histtype(&name) {
Some(t) => t,
None => {
rettv.vval = v_string(String::new());
return;
}
};
let idx = if argvars.len() >= 2 {
tv_get_number(&argvars[1])
} else {
0
};
let s = HISTORY.with(|h| {
let h = h.borrow();
let v = &h[t];
let n = v.len() as varnumber_T;
let pos: Option<usize> = if idx == 0 {
v.len().checked_sub(1)
} else if idx > 0 {
if idx <= n {
Some((idx - 1) as usize)
} else {
None
}
} else {
let p = n + idx;
if p >= 0 {
Some(p as usize)
} else {
None
}
};
pos.map(|i| v[i].clone()).unwrap_or_default()
});
rettv.vval = v_string(s);
}
pub fn f_histnr(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
match get_histtype(&name) {
Some(t) => {
let n = HISTORY.with(|h| h.borrow()[t].len());
rettv.vval = v_number(n as varnumber_T);
}
None => rettv.vval = v_number(-1),
}
}
pub fn f_histdel(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
let t = match get_histtype(&name) {
Some(t) => t,
None => return,
};
HISTORY.with(|h| {
let mut h = h.borrow_mut();
if argvars.len() < 2 {
h[t].clear();
} else if argvars[1].v_type == VAR_NUMBER {
let idx = tv_get_number(&argvars[1]);
let n = h[t].len() as varnumber_T;
let pos = if idx > 0 && idx <= n {
Some((idx - 1) as usize)
} else if idx < 0 && n + idx >= 0 {
Some((n + idx) as usize)
} else {
None
};
if let Some(p) = pos {
h[t].remove(p);
}
} else {
let pat = tv_get_string(&argvars[1]);
h[t].retain(|e| !regex_match(&pat, e, false));
}
});
rettv.vval = v_number(1);
}
thread_local! {
static USER_DIGRAPHS: std::cell::RefCell<std::collections::HashMap<String, String>> =
std::cell::RefCell::new(std::collections::HashMap::new());
}
fn getexactdigraph(c1: char, c2: char) -> Option<String> {
let key: String = [c1, c2].iter().collect();
if let Some(v) = USER_DIGRAPHS.with(|d| d.borrow().get(&key).cloned()) {
return Some(v);
}
let builtin = match (c1, c2) {
('a', ':') => 'ä',
('o', ':') => 'ö',
('u', ':') => 'ü',
('e', '\'') => 'é',
('C', 'o') => '©',
('R', 'O') => '®',
('+', '-') => '±',
('-', '>') => '→',
('O', 'K') => '✓',
_ => return None,
};
Some(builtin.to_string())
}
pub fn f_digraph_get(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let chars = tv_get_string(&argvars[0]);
let cc: Vec<char> = chars.chars().collect();
if cc.len() != 2 {
crate::ported::message::semsg(&format!(
"E1214: Digraph must be just two characters: {chars}"
));
rettv.vval = v_string(String::new());
return;
}
rettv.vval = v_string(getexactdigraph(cc[0], cc[1]).unwrap_or_default());
}
pub fn f_digraph_set(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_BOOL;
let chars = tv_get_string(&argvars[0]);
let digr = tv_get_string(&argvars[1]);
if chars.chars().count() != 2 {
crate::ported::message::semsg(&format!(
"E1214: Digraph must be just two characters: {chars}"
));
rettv.vval = v_bool(kBoolVarFalse);
return;
}
USER_DIGRAPHS.with(|d| d.borrow_mut().insert(chars, digr));
rettv.vval = v_bool(kBoolVarTrue);
}
pub fn f_digraph_getlist(_argvars: &[typval_T], rettv: &mut typval_T) {
let mut entries: Vec<(String, String)> = USER_DIGRAPHS.with(|d| {
d.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
});
entries.sort();
let out = tv_list_alloc_ret(rettv, entries.len() as isize);
let mut ob = out.borrow_mut();
for (chars, digr) in entries {
let pair = tv_list_alloc(2);
{
let mut pb = pair.borrow_mut();
tv_list_append_string(&mut pb, &chars);
tv_list_append_string(&mut pb, &digr);
}
tv_list_append_tv(
&mut ob,
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(pair)),
},
);
}
}
pub fn f_digraph_setlist(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_BOOL;
rettv.vval = v_bool(kBoolVarFalse);
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
_ => {
emsg("E714: List required");
return;
}
};
let mut pending: Vec<(String, String)> = Vec::new();
for item in l.borrow().lv_items.iter() {
let pair: Vec<String> = match (item.li_tv.v_type, &item.li_tv.vval) {
(VAR_LIST, v_list(Some(inner))) => inner
.borrow()
.lv_items
.iter()
.map(|e| tv_get_string(&e.li_tv))
.collect(),
_ => {
emsg("E1216: digraph_setlist() argument must be a list of lists with two items");
return;
}
};
if pair.len() != 2 || pair[0].chars().count() != 2 {
emsg("E1216: digraph_setlist() argument must be a list of lists with two items");
return;
}
pending.push((pair[0].clone(), pair[1].clone()));
}
USER_DIGRAPHS.with(|d| {
let mut d = d.borrow_mut();
for (k, v) in pending {
d.insert(k, v);
}
});
rettv.vval = v_bool(kBoolVarTrue);
}
pub fn f_hostname(_argvars: &[typval_T], rettv: &mut typval_T) {
use nix::libc;
rettv.v_type = VAR_STRING;
let mut buf = [0u8; 256];
let name = unsafe {
if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) == 0 {
std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
.to_string_lossy()
.into_owned()
} else {
String::new()
}
};
rettv.vval = v_string(name);
}
pub fn f_iconv(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let s = tv_get_string(&argvars[0]);
let canon = |e: &str| -> &'static str {
match e.to_ascii_lowercase().as_str() {
"utf-8" | "utf8" | "unicode" => "utf-8",
"latin1" | "iso-8859-1" | "8bit-iso-8859-1" => "latin1",
_ => "other",
}
};
let from = canon(&tv_get_string(&argvars[1]));
let to = canon(&tv_get_string(&argvars[2]));
let out = if from == to {
s
} else if from == "latin1" && to == "utf-8" {
s
} else if from == "utf-8" && to == "latin1" {
s.chars()
.map(|c| if (c as u32) <= 0xFF { c } else { '?' })
.collect()
} else {
s
};
rettv.vval = v_string(out);
}
thread_local! {
static ARGLIST: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
}
pub fn set_arglist(args: &[String]) {
ARGLIST.with(|a| *a.borrow_mut() = args.to_vec());
}
pub fn f_argc(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(ARGLIST.with(|a| a.borrow().len()) as varnumber_T);
}
pub fn f_argidx(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_argv(argvars: &[typval_T], rettv: &mut typval_T) {
if !argvars.is_empty() && tv_get_number(&argvars[0]) != -1 {
let idx = tv_get_number(&argvars[0]);
rettv.v_type = VAR_STRING;
let s = ARGLIST.with(|a| {
let a = a.borrow();
if idx >= 0 && (idx as usize) < a.len() {
a[idx as usize].clone()
} else {
String::new()
}
});
rettv.vval = v_string(s);
return;
}
let l = tv_list_alloc_ret(rettv, ARGLIST.with(|a| a.borrow().len()) as isize);
let mut lb = l.borrow_mut();
ARGLIST.with(|a| {
for s in a.borrow().iter() {
tv_list_append_string(&mut lb, s);
}
});
}
pub fn f_assert_equalfile(argvars: &[typval_T], rettv: &mut typval_T) {
let fname1 = tv_get_string(&argvars[0]);
let fname2 = tv_get_string(&argvars[1]);
let c1 = std::fs::read(&fname1);
let c2 = std::fs::read(&fname2);
let equal = matches!((&c1, &c2), (Ok(a), Ok(b)) if a == b);
if equal {
return;
}
let detail = match (c1.is_ok(), c2.is_ok()) {
(false, _) => format!("E485: Can't read file {fname1}"),
(_, false) => format!("E485: Can't read file {fname2}"),
_ => format!("first file \"{fname1}\" differs from second file \"{fname2}\""),
};
let extra = if argvars.len() >= 3 {
format!("{}: ", tv_get_string(&argvars[2]))
} else {
String::new()
};
assert_error(&format!("{extra}{detail}"));
rettv.vval = v_number(1);
}
pub fn f_arglistid(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_foldlevel(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
thread_local! {
static MATCHES: std::cell::RefCell<Vec<typval_T>> = const { std::cell::RefCell::new(Vec::new()) };
static MATCH_LAST_ID: std::cell::Cell<varnumber_T> = const { std::cell::Cell::new(1000) };
}
fn match_dict_val(
d: std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>,
) -> typval_T {
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(d)),
}
}
pub fn f_matchadd(argvars: &[typval_T], rettv: &mut typval_T) {
let group = tv_get_string(&argvars[0]);
let pattern = tv_get_string(&argvars[1]);
let priority = if argvars.len() >= 3 {
tv_get_number(&argvars[2])
} else {
10
};
let mut id = if argvars.len() >= 4 {
tv_get_number(&argvars[3])
} else {
-1
};
if id == 0 || id < -1 {
crate::ported::message::semsg(&format!("E799: Invalid ID: {id}"));
rettv.vval = v_number(-1);
return;
}
if id == -1 {
id = MATCH_LAST_ID.with(|c| {
let v = c.get() + 1;
c.set(v);
v
});
}
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "group", &group);
tv_dict_add_str(&mut db, "pattern", &pattern);
tv_dict_add_nr(&mut db, "priority", priority);
tv_dict_add_nr(&mut db, "id", id);
}
MATCHES.with(|m| m.borrow_mut().push(match_dict_val(d)));
rettv.vval = v_number(id);
}
pub fn f_matchaddpos(argvars: &[typval_T], rettv: &mut typval_T) {
let group = tv_get_string(&argvars[0]);
let priority = if argvars.len() >= 3 {
tv_get_number(&argvars[2])
} else {
10
};
let mut id = if argvars.len() >= 4 {
tv_get_number(&argvars[3])
} else {
-1
};
if id == 0 || id < -1 {
crate::ported::message::semsg(&format!("E799: Invalid ID: {id}"));
rettv.vval = v_number(-1);
return;
}
if id == -1 {
id = MATCH_LAST_ID.with(|c| {
let v = c.get() + 1;
c.set(v);
v
});
}
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "group", &group);
tv_dict_add_nr(&mut db, "priority", priority);
tv_dict_add_nr(&mut db, "id", id);
if let (VAR_LIST, v_list(Some(l))) = (argvars[1].v_type, &argvars[1].vval) {
for (i, it) in l.borrow().lv_items.iter().enumerate() {
tv_dict_add_tv(&mut db, &format!("pos{}", i + 1), it.li_tv.clone());
}
}
}
MATCHES.with(|m| m.borrow_mut().push(match_dict_val(d)));
rettv.vval = v_number(id);
}
pub fn f_matchdelete(argvars: &[typval_T], rettv: &mut typval_T) {
let id = tv_get_number(&argvars[0]);
let removed = MATCHES.with(|m| {
let mut m = m.borrow_mut();
let before = m.len();
m.retain(|tv| match (tv.v_type, &tv.vval) {
(VAR_DICT, v_dict(Some(d))) => {
tv_dict_find(&d.borrow(), "id").map(tv_get_number) != Some(id)
}
_ => true,
});
before != m.len()
});
if !removed {
crate::ported::message::semsg(&format!("E803: ID not found: {id}"));
rettv.vval = v_number(-1);
}
}
pub fn f_getmatches(_argvars: &[typval_T], rettv: &mut typval_T) {
let snapshot = MATCHES.with(|m| m.borrow().clone());
let out = tv_list_alloc_ret(rettv, snapshot.len() as isize);
let mut ob = out.borrow_mut();
for tv in snapshot {
tv_list_append_tv(&mut ob, tv);
}
}
pub fn f_setmatches(argvars: &[typval_T], rettv: &mut typval_T) {
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
_ => {
emsg("E714: List required");
rettv.vval = v_number(-1);
return;
}
};
let mut next: Vec<typval_T> = Vec::new();
for it in l.borrow().lv_items.iter() {
if it.li_tv.v_type != VAR_DICT {
emsg("E715: Dictionary required");
rettv.vval = v_number(-1);
return;
}
next.push(it.li_tv.clone());
}
MATCHES.with(|m| *m.borrow_mut() = next);
}
pub fn f_clearmatches(_argvars: &[typval_T], _rettv: &mut typval_T) {
MATCHES.with(|m| m.borrow_mut().clear());
}
pub fn f_matcharg(argvars: &[typval_T], rettv: &mut typval_T) {
let nr = tv_get_number(&argvars[0]);
let l = tv_list_alloc_ret(rettv, 2);
if (1..=3).contains(&nr) {
let mut lb = l.borrow_mut();
tv_list_append_string(&mut lb, "");
tv_list_append_string(&mut lb, "");
}
}
thread_local! {
static SIGNS: std::cell::RefCell<std::collections::BTreeMap<String, typval_T>> =
const { std::cell::RefCell::new(std::collections::BTreeMap::new()) };
}
pub fn f_sign_define(argvars: &[typval_T], rettv: &mut typval_T) {
if argvars[0].v_type == VAR_LIST {
emsg("E1206: Dictionary required");
rettv.vval = v_number(-1);
return;
}
let name = tv_get_string(&argvars[0]);
if name.is_empty() {
rettv.vval = v_number(-1);
return;
}
let opts = if argvars.len() >= 2 && argvars[1].v_type == VAR_DICT {
argvars[1].clone()
} else {
let d = tv_dict_alloc();
match_dict_val(d)
};
SIGNS.with(|s| s.borrow_mut().insert(name, opts));
}
pub fn f_sign_getdefined(argvars: &[typval_T], rettv: &mut typval_T) {
let want = if argvars.is_empty() {
None
} else {
Some(tv_get_string(&argvars[0]))
};
let entries: Vec<(String, typval_T)> = SIGNS.with(|s| {
s.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
});
let out = tv_list_alloc_ret(rettv, 0);
let mut ob = out.borrow_mut();
for (name, opts) in entries {
if let Some(w) = &want {
if w != &name {
continue;
}
}
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "name", &name);
if let (VAR_DICT, v_dict(Some(src))) = (opts.v_type, &opts.vval) {
for (k, v) in src.borrow().dv_hashtab.iter() {
tv_dict_add_tv(&mut db, k, v.clone());
}
}
}
tv_list_append_tv(&mut ob, match_dict_val(d));
}
}
pub fn f_sign_undefine(argvars: &[typval_T], rettv: &mut typval_T) {
if argvars.is_empty() {
SIGNS.with(|s| s.borrow_mut().clear());
return;
}
let name = tv_get_string(&argvars[0]);
let existed = SIGNS.with(|s| s.borrow_mut().remove(&name).is_some());
if !existed {
crate::ported::message::semsg(&format!("E155: Unknown sign: {name}"));
rettv.vval = v_number(-1);
}
}
pub fn f_foldclosed(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
}
pub fn f_foldclosedend(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
}
const MAP_NORMAL: i32 = 0x01;
const MAP_VISUAL: i32 = 0x02;
const MAP_SELECT: i32 = 0x04;
const MAP_OP_PENDING: i32 = 0x08;
const MAP_INSERT: i32 = 0x10;
const MAP_CMDLINE: i32 = 0x20;
const MAP_LANG: i32 = 0x40;
const MAP_TERMINAL: i32 = 0x80;
const MAP_DEFAULT: i32 = MAP_NORMAL | MAP_VISUAL | MAP_SELECT | MAP_OP_PENDING;
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct mapblock_T {
pub lhs: String,
pub rhs: String,
pub mode: i32,
pub noremap: bool,
pub expr: bool,
pub silent: bool,
pub nowait: bool,
pub buffer: bool,
pub sid: varnumber_T,
pub lnum: varnumber_T,
pub desc: String,
}
thread_local! {
static MAPHASH: std::cell::RefCell<Vec<mapblock_T>> =
const { std::cell::RefCell::new(Vec::new()) };
}
fn mode_str2flags(modechars: &str) -> i32 {
if modechars.is_empty() || modechars == " " {
return MAP_DEFAULT;
}
let mut f = 0;
for c in modechars.chars() {
f |= match c {
'n' => MAP_NORMAL,
'v' => MAP_VISUAL | MAP_SELECT,
'x' => MAP_VISUAL,
's' => MAP_SELECT,
'o' => MAP_OP_PENDING,
'i' => MAP_INSERT,
'c' => MAP_CMDLINE,
'l' => MAP_LANG,
't' => MAP_TERMINAL,
'!' => MAP_INSERT | MAP_CMDLINE,
_ => 0,
};
}
f
}
fn map_mode_to_chars(mode: i32) -> String {
if mode == MAP_DEFAULT {
return " ".to_string();
}
if mode == MAP_INSERT | MAP_CMDLINE {
return "!".to_string();
}
if mode == MAP_VISUAL | MAP_SELECT {
return "v".to_string();
}
let mut s = String::new();
for (bit, c) in [
(MAP_NORMAL, 'n'),
(MAP_VISUAL, 'x'),
(MAP_SELECT, 's'),
(MAP_OP_PENDING, 'o'),
(MAP_INSERT, 'i'),
(MAP_CMDLINE, 'c'),
(MAP_LANG, 'l'),
(MAP_TERMINAL, 't'),
] {
if mode & bit != 0 {
s.push(c);
}
}
s
}
fn mapblock_fill_dict(mb: &mapblock_T) -> typval_T {
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "lhs", &mb.lhs);
tv_dict_add_str(&mut db, "lhsraw", &mb.lhs);
tv_dict_add_str(&mut db, "rhs", &mb.rhs);
tv_dict_add_nr(&mut db, "noremap", mb.noremap as varnumber_T);
tv_dict_add_nr(&mut db, "expr", mb.expr as varnumber_T);
tv_dict_add_nr(&mut db, "silent", mb.silent as varnumber_T);
tv_dict_add_nr(&mut db, "nowait", mb.nowait as varnumber_T);
tv_dict_add_nr(&mut db, "buffer", mb.buffer as varnumber_T);
tv_dict_add_nr(&mut db, "script", 0);
tv_dict_add_nr(&mut db, "sid", mb.sid);
tv_dict_add_nr(&mut db, "scriptversion", 1);
tv_dict_add_nr(&mut db, "lnum", mb.lnum);
tv_dict_add_str(&mut db, "mode", &map_mode_to_chars(mb.mode));
tv_dict_add_nr(&mut db, "mode_bits", mb.mode as varnumber_T);
tv_dict_add_str(&mut db, "desc", &mb.desc);
tv_dict_add_nr(&mut db, "abbr", 0);
}
match_dict_val(d)
}
fn map_add(mb: mapblock_T) {
MAPHASH.with(|h| {
let mut h = h.borrow_mut();
h.retain(|e| !(e.lhs == mb.lhs && e.mode & mb.mode != 0));
h.push(mb);
});
}
fn getmaparg(name: &str, mode: i32, want_dict: bool, rettv: &mut typval_T) {
let found = MAPHASH.with(|h| {
h.borrow()
.iter()
.find(|e| e.lhs == name && e.mode & mode != 0)
.cloned()
});
if want_dict {
match found {
Some(mb) => *rettv = mapblock_fill_dict(&mb),
None => {
let _ = tv_dict_alloc_ret(rettv);
}
}
} else {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(found.map(|m| m.rhs).unwrap_or_default());
}
}
pub fn f_hasmapto(argvars: &[typval_T], rettv: &mut typval_T) {
let what = tv_get_string(&argvars[0]);
let mode = mode_str2flags(&argvars.get(1).map(tv_get_string).unwrap_or_default());
let found = MAPHASH.with(|h| {
h.borrow()
.iter()
.any(|e| e.mode & mode != 0 && e.rhs.contains(&what))
});
rettv.vval = v_number(found as varnumber_T);
}
pub fn f_maparg(argvars: &[typval_T], rettv: &mut typval_T) {
let name = tv_get_string(&argvars[0]);
let mode = mode_str2flags(&argvars.get(1).map(tv_get_string).unwrap_or_default());
let want_dict = argvars.len() >= 4 && tv_get_bool(&argvars[3]) != 0;
getmaparg(&name, mode, want_dict, rettv);
}
pub fn f_mapcheck(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let name = tv_get_string(&argvars[0]);
let mode = mode_str2flags(&argvars.get(1).map(tv_get_string).unwrap_or_default());
let rhs = MAPHASH.with(|h| {
h.borrow()
.iter()
.find(|e| e.mode & mode != 0 && (e.lhs.starts_with(&name) || name.starts_with(&e.lhs)))
.map(|e| e.rhs.clone())
});
rettv.vval = v_string(rhs.unwrap_or_default());
}
pub fn f_maplist(_argvars: &[typval_T], rettv: &mut typval_T) {
let maps = MAPHASH.with(|h| h.borrow().clone());
let l = tv_list_alloc_ret(rettv, maps.len() as isize);
let mut lb = l.borrow_mut();
for mb in &maps {
tv_list_append_tv(&mut lb, mapblock_fill_dict(mb));
}
}
pub fn get_map_mode(cmd: &str) -> Option<(i32, bool, bool, bool)> {
let (base, bang) = match cmd.strip_suffix('!') {
Some(b) => (b, true),
None => (cmd, false),
};
let (prefix, unmap, clear, noremap) = if let Some(p) = base.strip_suffix("mapclear") {
(p, false, true, false)
} else if let Some(p) = base.strip_suffix("noremap") {
(p, false, false, true)
} else if let Some(p) = base.strip_suffix("unmap") {
(p, true, false, false)
} else if let Some(p) = base.strip_suffix("map") {
(p, false, false, false)
} else {
return None;
};
let mode = match prefix {
"" => {
if bang {
MAP_INSERT | MAP_CMDLINE
} else {
MAP_DEFAULT
}
}
"n" => MAP_NORMAL,
"i" => MAP_INSERT,
"v" => MAP_VISUAL | MAP_SELECT,
"x" => MAP_VISUAL,
"s" => MAP_SELECT,
"o" => MAP_OP_PENDING,
"c" => MAP_CMDLINE,
"t" => MAP_TERMINAL,
"l" => MAP_LANG,
_ => return None,
};
Some((mode, unmap, clear, noremap))
}
pub fn do_map(arg: &str, mode: i32, unmap: bool, clear: bool, noremap: bool) {
if clear {
MAPHASH.with(|h| h.borrow_mut().retain(|e| e.mode & mode == 0));
return;
}
let mut rest = arg.trim_start();
let (mut silent, mut expr, mut nowait, mut buffer) = (false, false, false, false);
loop {
let lower = rest.to_ascii_lowercase();
if let Some(r) = lower.strip_prefix("<silent>") {
silent = true;
rest = &rest[rest.len() - r.len()..];
} else if let Some(r) = lower.strip_prefix("<expr>") {
expr = true;
rest = &rest[rest.len() - r.len()..];
} else if let Some(r) = lower.strip_prefix("<nowait>") {
nowait = true;
rest = &rest[rest.len() - r.len()..];
} else if let Some(r) = lower.strip_prefix("<buffer>") {
buffer = true;
rest = &rest[rest.len() - r.len()..];
} else if let Some(r) = lower
.strip_prefix("<unique>")
.or(lower.strip_prefix("<script>"))
{
rest = &rest[rest.len() - r.len()..];
} else {
break;
}
rest = rest.trim_start();
}
let (lhs, rhs) = match rest.find(char::is_whitespace) {
Some(i) => (&rest[..i], rest[i..].trim()),
None => (rest, ""),
};
if lhs.is_empty() {
return;
}
if unmap {
MAPHASH.with(|h| {
h.borrow_mut()
.retain(|e| !(e.lhs == lhs && e.mode & mode != 0))
});
return;
}
if rhs.is_empty() {
return;
}
map_add(mapblock_T {
lhs: lhs.to_string(),
rhs: rhs.to_string(),
mode,
noremap,
expr,
silent,
nowait,
buffer,
sid: 0,
lnum: 0,
desc: String::new(),
});
}
#[allow(non_camel_case_types)]
#[derive(Clone)]
pub struct ucmd_T {
pub rep: String,
pub nargs: char,
pub bang: bool,
}
thread_local! {
static USER_COMMANDS: std::cell::RefCell<std::collections::BTreeMap<String, ucmd_T>> =
const { std::cell::RefCell::new(std::collections::BTreeMap::new()) };
}
fn uc_add_command(name: &str, rep: &str, nargs: char, bang: bool) {
USER_COMMANDS.with(|c| {
c.borrow_mut().insert(
name.to_string(),
ucmd_T {
rep: rep.to_string(),
nargs,
bang,
},
);
});
}
fn find_ucmd(name: &str) -> Option<ucmd_T> {
USER_COMMANDS.with(|c| {
let c = c.borrow();
if let Some(uc) = c.get(name) {
return Some(uc.clone());
}
let mut hits = c.iter().filter(|(k, _)| k.starts_with(name));
match (hits.next(), hits.next()) {
(Some((_, uc)), None) => Some(uc.clone()),
_ => None,
}
})
}
fn uc_check_code(rep: &str, args: &str, bang: bool) -> String {
let quote = |s: &str| format!("'{}'", s.replace('\'', "''"));
let chars: Vec<char> = rep.chars().collect();
let mut out = String::new();
let mut i = 0;
while i < chars.len() {
if chars[i] == '<' {
if let Some(off) = chars[i..].iter().position(|&c| c == '>') {
let code: String = chars[i + 1..i + off].iter().collect();
let repl = match code.to_ascii_lowercase().as_str() {
"args" => Some(args.to_string()),
"q-args" => Some(quote(args)),
"f-args" => Some(
args.split_whitespace()
.map(quote)
.collect::<Vec<_>>()
.join(", "),
),
"bang" => Some(if bang { "!".to_string() } else { String::new() }),
"lt" => Some("<".to_string()),
_ => None,
};
if let Some(r) = repl {
out.push_str(&r);
i += off + 1;
continue;
}
}
}
out.push(chars[i]);
i += 1;
}
out
}
pub fn do_ucmd(name: &str, args: &str, bang: bool) -> Option<String> {
let uc = find_ucmd(name)?;
Some(uc_check_code(&uc.rep, args, bang && uc.bang))
}
pub fn ex_command(arg: &str) {
let mut s = arg.trim();
s = s.strip_prefix('!').unwrap_or(s).trim_start();
let mut nargs = '0';
let mut bang = false;
while let Some(r) = s.strip_prefix('-') {
let end = r.find(char::is_whitespace).unwrap_or(r.len());
let attr = r[..end].to_ascii_lowercase();
if let Some(v) = attr.strip_prefix("nargs=") {
nargs = v.chars().next().unwrap_or('0');
} else if attr == "bang" {
bang = true;
}
s = r[end..].trim_start();
}
let end = s.find(char::is_whitespace).unwrap_or(s.len());
let name = &s[..end];
let rep = s[end..].trim_start();
if name.is_empty() {
return;
}
uc_add_command(name, rep, nargs, bang);
}
pub fn ex_delcommand(arg: &str) {
let name = arg.trim();
USER_COMMANDS.with(|c| {
c.borrow_mut().remove(name);
});
}
#[derive(Clone)]
pub struct AutoCmd {
pub group: String,
pub event: String,
pub pat: String,
pub cmd: String,
}
thread_local! {
static AUTOCMDS: std::cell::RefCell<Vec<AutoCmd>> = const { std::cell::RefCell::new(Vec::new()) };
static CURRENT_AUGROUP: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
}
fn match_file_pat(pat: &str, name: &str) -> bool {
let p: Vec<char> = pat.chars().collect();
let s: Vec<char> = name.chars().collect();
let (mut pi, mut si) = (0usize, 0usize);
let (mut star, mut mark): (Option<usize>, usize) = (None, 0);
while si < s.len() {
if pi < p.len() && (p[pi] == '?' || p[pi] == s[si]) {
pi += 1;
si += 1;
} else if pi < p.len() && p[pi] == '*' {
star = Some(pi);
mark = si;
pi += 1;
} else if let Some(sp) = star {
pi = sp + 1;
mark += 1;
si = mark;
} else {
return false;
}
}
while pi < p.len() && p[pi] == '*' {
pi += 1;
}
pi == p.len()
}
pub fn do_augroup(name: &str) {
let name = name.trim();
let g = if name.is_empty() || name.eq_ignore_ascii_case("end") {
String::new()
} else {
name.to_string()
};
CURRENT_AUGROUP.with(|c| *c.borrow_mut() = g);
}
pub fn do_autocmd(arg: &str) {
let mut s = arg.trim();
let force = s.starts_with('!');
s = s.strip_prefix('!').unwrap_or(s).trim_start();
let group = CURRENT_AUGROUP.with(|c| c.borrow().clone());
if s.is_empty() {
if force {
AUTOCMDS.with(|a| a.borrow_mut().retain(|ac| ac.group != group));
}
return;
}
let (events_tok, rest) = match s.find(char::is_whitespace) {
Some(i) => (&s[..i], s[i..].trim_start()),
None => (s, ""),
};
let events: Vec<String> = events_tok
.split(',')
.filter(|e| !e.is_empty())
.map(|e| e.to_ascii_lowercase())
.collect();
if rest.is_empty() {
if force {
AUTOCMDS.with(|a| {
a.borrow_mut()
.retain(|ac| !(ac.group == group && events.contains(&ac.event)))
});
}
return;
}
let (pat, cmd) = match rest.find(char::is_whitespace) {
Some(i) => (&rest[..i], rest[i..].trim()),
None => (rest, ""),
};
if cmd.is_empty() {
if force {
AUTOCMDS.with(|a| {
a.borrow_mut().retain(|ac| {
!(ac.group == group && events.contains(&ac.event) && ac.pat == pat)
})
});
}
return;
}
AUTOCMDS.with(|a| {
let mut a = a.borrow_mut();
for event in &events {
if force {
a.retain(|ac| !(ac.group == group && &ac.event == event && ac.pat == pat));
}
a.push(AutoCmd {
group: group.clone(),
event: event.clone(),
pat: pat.to_string(),
cmd: cmd.to_string(),
});
}
});
}
pub fn apply_autocmds(event: &str, target: &str) -> Vec<String> {
let evt = event.to_ascii_lowercase();
AUTOCMDS.with(|a| {
a.borrow()
.iter()
.filter(|ac| ac.event == evt && match_file_pat(&ac.pat, target))
.map(|ac| ac.cmd.clone())
.collect()
})
}
pub fn do_doautocmd(arg: &str) -> Vec<String> {
let s = arg.trim();
let (event, target) = match s.find(char::is_whitespace) {
Some(i) => (&s[..i], s[i..].trim()),
None => (s, "*"),
};
if event.is_empty() {
return Vec::new();
}
apply_autocmds(event, target)
}
pub fn au_exists(arg: &str) -> bool {
let mut parts = arg.splitn(2, '#');
let event = parts.next().unwrap_or("").to_ascii_lowercase();
let pat = parts.next();
AUTOCMDS.with(|a| {
#[allow(clippy::unnecessary_map_or)]
a.borrow()
.iter()
.any(|ac| ac.event == event && pat.map_or(true, |p| ac.pat == p))
})
}
pub enum ExCmdResult {
Handled,
NotEx,
Global(Vec<varnumber_T>, String),
}
fn parse_addr(s: &str) -> (varnumber_T, usize, bool) {
let b = s.as_bytes();
let mut i = 0;
let last = curbuf_len();
let cur = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow())).0;
let (mut lnum, had) = if i < b.len() && b[i] == b'.' {
i += 1;
(cur, true)
} else if i < b.len() && b[i] == b'$' {
i += 1;
(last, true)
} else if i + 1 < b.len() && b[i] == b'\'' {
let name = b[i + 1] as char;
i += 2;
(getmark(name).map_or(0, |(l, _)| l), true)
} else if i < b.len() && b[i].is_ascii_digit() {
let start = i;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
(s[start..i].parse().unwrap_or(0), true)
} else {
(cur, false)
};
let mut had_off = false;
while i < b.len() && (b[i] == b'+' || b[i] == b'-') {
let sign = if b[i] == b'+' { 1 } else { -1 };
i += 1;
let start = i;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
let n: varnumber_T = if i > start {
s[start..i].parse().unwrap_or(0)
} else {
1
};
lnum += sign * n;
had_off = true;
}
(lnum, i, had || had_off)
}
fn parse_line_range(s: &str) -> (varnumber_T, varnumber_T, bool, &str) {
let s = s.trim_start();
if let Some(rest) = s.strip_prefix('%') {
return (1, curbuf_len(), true, rest.trim_start());
}
let cur = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow())).0;
let (a1, c1, had1) = parse_addr(s);
let mut rest = &s[c1..];
if let Some(after) = rest.strip_prefix([',', ';']) {
let (a2, c2, _) = parse_addr(after);
rest = &after[c2..];
let l1 = if had1 { a1 } else { cur };
return (l1, a2, true, rest.trim_start());
}
if had1 {
(a1, a1, true, rest.trim_start())
} else {
(cur, cur, false, rest.trim_start())
}
}
fn split_delim(s: &str, max: usize) -> Vec<String> {
let mut chars = s.chars();
let delim = match chars.next() {
Some(d) => d,
None => return Vec::new(),
};
let mut fields = vec![String::new()];
let mut escaped = false;
for c in chars {
if escaped {
if c != delim {
fields.last_mut().unwrap().push('\\');
}
fields.last_mut().unwrap().push(c);
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == delim && fields.len() < max {
fields.push(String::new());
} else {
fields.last_mut().unwrap().push(c);
}
}
fields
}
pub fn do_excmd(line: &str) -> ExCmdResult {
let line = line.trim().strip_prefix(':').unwrap_or(line.trim());
let (l1, l2, had_range, rest) = parse_line_range(line);
let cmd_end = rest
.find(|c: char| !c.is_ascii_alphabetic())
.unwrap_or(rest.len());
let cmd = &rest[..cmd_end];
let bang = rest[cmd_end..].starts_with('!');
let args = rest[cmd_end + if bang { 1 } else { 0 }..].trim();
let len = curbuf_len();
let (lo, hi) = (l1.clamp(1, len), l2.clamp(1, len));
match cmd {
"d" | "de" | "del" | "delete" => {
CURBUF.with(|b| {
let mut b = b.borrow_mut();
if b.is_empty() {
b.push(String::new());
}
let (a, z) = ((lo - 1) as usize, (hi as usize).min(b.len()));
if a < z {
b.drain(a..z);
}
if b.is_empty() {
b.push(String::new());
}
});
set_cursorpos(lo, 1);
ExCmdResult::Handled
}
"s" | "su" | "sub" | "substitute" => {
let f = split_delim(args, 3);
let (pat, sub, flags) = (
f.first().cloned().unwrap_or_default(),
f.get(1).cloned().unwrap_or_default(),
f.get(2).cloned().unwrap_or_default(),
);
for lnum in lo..=hi {
let cur = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
let new = crate::viml_regex::regex_substitute(&cur, &pat, &sub, &flags);
set_buffer_lines(lnum, vec![new], false);
}
set_cursorpos(hi, 1);
ExCmdResult::Handled
}
"g" | "gl" | "global" | "v" | "vglobal" => {
let invert = cmd.starts_with('v') || bang;
let f = split_delim(args, 2);
let pat = f.first().cloned().unwrap_or_default();
let sub_cmd = f.get(1).cloned().unwrap_or_default();
let ic = tv_get_number_chk(&get_option_value("ignorecase"), None) != 0;
let (gl, gh) = if had_range { (lo, hi) } else { (1, len) };
let mut hits = Vec::new();
for lnum in gl..=gh {
let l = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
let m = crate::viml_regex::regex_match(&pat, &l, ic);
if m != invert {
hits.push(lnum);
}
}
ExCmdResult::Global(hits, sub_cmd)
}
"m" | "mo" | "move" => {
let (dest, _, _) = parse_addr(args.trim());
ex_move(lo, hi, dest);
ExCmdResult::Handled
}
"t" | "co" | "cop" | "copy" => {
let (dest, _, _) = parse_addr(args.trim());
ex_copy(lo, hi, dest);
ExCmdResult::Handled
}
"j" | "jo" | "join" => {
ex_join(lo, hi, bang);
ExCmdResult::Handled
}
"y" | "ya" | "yank" => {
let reg = args.chars().next().unwrap_or('"');
let lines = get_buffer_lines(lo, hi);
write_reg_contents_lst(reg, lines, MotionType::LineWise, false);
ExCmdResult::Handled
}
"pu" | "put" => {
let reg = args.chars().next().unwrap_or('"');
if let Some(lines) = get_reg_contents(reg) {
set_buffer_lines(hi, lines, true);
}
ExCmdResult::Handled
}
"r" | "re" | "read" => {
let at = if had_range { hi } else { curbuf_len() };
let lines = if let Some(shell) = args.strip_prefix('!') {
shell_capture(shell.trim())
} else {
std::fs::read_to_string(args)
.map(|s| s.lines().map(str::to_string).collect())
.unwrap_or_default()
};
set_buffer_lines(at, lines, true);
ExCmdResult::Handled
}
"w" | "wr" | "write" => {
let (lo2, hi2) = if had_range { (lo, hi) } else { (1, len) };
let body = get_buffer_lines(lo2, hi2).join("\n") + "\n";
if let Some(shell) = args.strip_prefix('!') {
shell_feed(shell.trim(), &body);
} else if let Some(file) = args.strip_prefix(">>") {
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(file.trim())
{
use std::io::Write;
let _ = f.write_all(body.as_bytes());
}
} else if !args.is_empty() {
let _ = std::fs::write(args, body);
}
ExCmdResult::Handled
}
"e" | "ed" | "edit" => {
if !args.is_empty() {
if let Ok(s) = std::fs::read_to_string(args) {
let lines: Vec<String> = s.lines().map(str::to_string).collect();
CURBUF.with(|b| {
*b.borrow_mut() = if lines.is_empty() {
vec![String::new()]
} else {
lines
}
});
set_cursorpos(1, 1);
}
}
ExCmdResult::Handled
}
"sort" | "sor" => {
let (lo2, hi2) = if had_range { (lo, hi) } else { (1, len) };
ex_sort(lo2, hi2, bang, args);
ExCmdResult::Handled
}
"mark" | "ma" | "mar" | "k" => {
if let Some(name) = args.chars().next() {
setmark(name, hi, 1);
}
ExCmdResult::Handled
}
"normal" | "norm" => {
let keys = rest[cmd_end + if bang { 1 } else { 0 }..]
.strip_prefix(' ')
.unwrap_or(args);
if had_range {
for lnum in lo..=hi.min(curbuf_len()) {
set_cursorpos(lnum, 1);
do_normal(keys);
}
} else {
do_normal(keys);
}
ExCmdResult::Handled
}
"echohl" | "echoh" => {
crate::ported::eval::ex_echohl(args);
ExCmdResult::Handled
}
"runtime" | "ru" | "run" | "runt" | "runti" | "runtim" => {
crate::fusevm_bridge::do_runtime(bang, args);
ExCmdResult::Handled
}
"redraw" | "redr" | "redra" | "redraws" | "redrawstatus" | "redrawt" | "redrawtabline"
| "redir" | "redi" => ExCmdResult::Handled,
"noh" | "nohl" | "nohls" | "nohlse" | "nohlsea" | "nohlsear" | "nohlsearc"
| "nohlsearch" => ExCmdResult::Handled,
"fold" | "fo" | "fol" | "foldopen" | "foldo" | "foldop" | "foldope" | "foldclose"
| "foldc" | "foldcl" | "foldclo" | "foldclos" => ExCmdResult::Handled,
"bnext" | "bn" | "bne" | "bprevious" | "bp" | "bprev" | "bNext" | "bN" | "bfirst"
| "bf" | "blast" | "bl" | "buffer" | "bu" | "buf" | "bmodified" | "bm" | "bmod"
| "ball" | "ba" => ExCmdResult::Handled,
"delmarks" | "delm" => {
if bang {
MARKS.with(|m| m.borrow_mut().clear());
} else {
for name in args.chars().filter(|c| !c.is_whitespace()) {
MARKS.with(|m| {
m.borrow_mut().remove(&name);
});
}
}
ExCmdResult::Handled
}
"p" | "pr" | "print" | "nu" | "number" => {
let numbered = cmd.starts_with("nu") || cmd == "number";
for lnum in lo..=hi {
let l = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
if numbered {
println!("{lnum:>3} {l}");
} else {
println!("{l}");
}
}
ExCmdResult::Handled
}
"" if rest.starts_with('>') || rest.starts_with('<') || rest.starts_with('!') => {
let c = rest.chars().next().unwrap();
if c == '!' {
if had_range {
let body = get_buffer_lines(lo, hi).join("\n") + "\n";
let out = shell_filter(rest[1..].trim(), &body);
CURBUF.with(|b| {
let mut b = b.borrow_mut();
let (a, z) = ((lo - 1) as usize, (hi as usize).min(b.len()));
b.drain(a..z);
for (i, l) in out.into_iter().enumerate() {
b.insert(a + i, l);
}
if b.is_empty() {
b.push(String::new());
}
});
set_cursorpos(lo, 1);
}
} else {
let count = rest.chars().take_while(|&x| x == c).count();
ex_shift(lo, hi, c == '>', count);
}
ExCmdResult::Handled
}
"" if had_range => {
set_cursorpos(hi, 1);
ExCmdResult::Handled
}
_ if crate::viml_parser::is_script_lang_cmd(rest) => ExCmdResult::Handled,
_ => ExCmdResult::NotEx,
}
}
fn ex_sort(lo: varnumber_T, hi: varnumber_T, reverse: bool, args: &str) {
let flags: String = args.chars().take_while(|c| *c != '/').collect();
let numeric = flags.contains('n');
let ignorecase = flags.contains('i');
let unique = flags.contains('u');
let pat: String = {
let f = split_delim(args.trim_start_matches(|c: char| c != '/'), 2);
f.first().cloned().unwrap_or_default()
};
let mut lines = get_buffer_lines(lo, hi);
let key = |s: &str| -> String {
if pat.is_empty() {
s.to_string()
} else {
let (_, _, e) = crate::viml_regex::regex_matchstrpos(&pat, s, ignorecase);
if e < 0 {
s.to_string()
} else {
s[e as usize..].to_string()
}
}
};
if numeric {
let num = |s: &str| -> i64 {
let k = key(s);
let t = k.trim_start();
let digits: String = t
.chars()
.skip_while(|c| !c.is_ascii_digit() && *c != '-')
.take_while(|c| c.is_ascii_digit() || *c == '-')
.collect();
digits.parse().unwrap_or(0)
};
lines.sort_by_key(|s| num(s));
} else if ignorecase {
lines.sort_by_key(|s| key(s).to_lowercase());
} else {
lines.sort_by_key(|s| key(s));
}
if reverse {
lines.reverse();
}
if unique {
lines.dedup();
}
CURBUF.with(|b| {
let mut b = b.borrow_mut();
let (a, z) = ((lo - 1) as usize, (hi as usize).min(b.len()));
if a < z {
b.drain(a..z);
for (i, l) in lines.into_iter().enumerate() {
b.insert(a + i, l);
}
}
if b.is_empty() {
b.push(String::new());
}
});
}
fn ex_shift(lo: varnumber_T, hi: varnumber_T, indent: bool, count: usize) {
let sw = {
let t = tv_get_number_chk(&get_option_value("shiftwidth"), None);
if t > 0 {
t as usize
} else {
8
}
};
let amount = sw * count.max(1);
for lnum in lo..=hi {
let line = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
let new = if indent {
if line.trim().is_empty() {
line
} else {
format!("{}{}", " ".repeat(amount), line)
}
} else {
let drop = line.chars().take(amount).take_while(|c| *c == ' ').count();
line[drop..].to_string()
};
set_buffer_lines(lnum, vec![new], false);
}
}
fn shell_capture(cmd: &str) -> Vec<String> {
std::process::Command::new("sh")
.arg("-c")
.arg(cmd)
.output()
.ok()
.map(|o| {
String::from_utf8_lossy(&o.stdout)
.lines()
.map(str::to_string)
.collect()
})
.unwrap_or_default()
}
fn shell_feed(cmd: &str, input: &str) {
use std::io::Write;
if let Ok(mut child) = std::process::Command::new("sh")
.arg("-c")
.arg(cmd)
.stdin(std::process::Stdio::piped())
.spawn()
{
if let Some(stdin) = child.stdin.as_mut() {
let _ = stdin.write_all(input.as_bytes());
}
let _ = child.wait();
}
}
fn shell_filter(cmd: &str, input: &str) -> Vec<String> {
use std::io::Write;
let child = std::process::Command::new("sh")
.arg("-c")
.arg(cmd)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn();
let mut child = match child {
Ok(c) => c,
Err(_) => return input.lines().map(str::to_string).collect(),
};
if let Some(stdin) = child.stdin.take() {
let mut stdin = stdin;
let _ = stdin.write_all(input.as_bytes());
}
child
.wait_with_output()
.map(|o| {
String::from_utf8_lossy(&o.stdout)
.lines()
.map(str::to_string)
.collect()
})
.unwrap_or_default()
}
fn ex_move(lo: varnumber_T, hi: varnumber_T, dest: varnumber_T) {
let moved = get_buffer_lines(lo, hi);
CURBUF.with(|b| {
let mut b = b.borrow_mut();
let (a, z) = ((lo - 1) as usize, (hi as usize).min(b.len()));
b.drain(a..z);
let mut at = dest;
if dest >= hi {
at -= hi - lo + 1;
} else if dest >= lo {
at = lo - 1;
}
let pos = (at.max(0) as usize).min(b.len());
for (i, l) in moved.into_iter().enumerate() {
b.insert(pos + i, l);
}
});
}
fn ex_copy(lo: varnumber_T, hi: varnumber_T, dest: varnumber_T) {
let copied = get_buffer_lines(lo, hi);
set_buffer_lines(dest, copied, true);
}
fn ex_join(lo: varnumber_T, hi: varnumber_T, keep: bool) {
let hi = hi.max(lo + 1);
let lines = get_buffer_lines(lo, hi);
if lines.len() < 2 {
return;
}
let mut joined = lines[0].clone();
for l in &lines[1..] {
if keep {
joined.push_str(l);
} else {
if !joined.is_empty() && !joined.ends_with(' ') {
joined.push(' ');
}
joined.push_str(l.trim_start());
}
}
CURBUF.with(|b| {
let mut b = b.borrow_mut();
let (a, z) = ((lo - 1) as usize, (hi as usize).min(b.len()));
if a < z {
b.drain(a..z);
b.insert(a, joined);
}
});
set_cursorpos(lo, 1);
}
fn char_class(c: char) -> u8 {
if c.is_whitespace() {
0
} else if c.is_alphanumeric() || c == '_' {
1
} else {
2
}
}
pub fn do_normal(keys: &str) {
let k: Vec<char> = keys.chars().collect();
let nlines = || curbuf_len();
let line_chars = |l: varnumber_T| -> Vec<char> {
get_buffer_lines(l, l)
.into_iter()
.next()
.unwrap_or_default()
.chars()
.collect()
};
let put_line = |l: varnumber_T, cs: &[char]| {
set_buffer_lines(l, vec![cs.iter().collect()], false);
};
let cur = || {
let __p = crate::fusevm_bridge::editor_curpos(CURPOS.with(|c| *c.borrow()));
(__p.0, __p.1)
};
let word_fwd = |l0: varnumber_T, ci0: usize| -> (varnumber_T, usize) {
let (mut l, mut ci) = (l0, ci0);
let mut line = line_chars(l);
if ci < line.len() {
let cls = char_class(line[ci]);
while ci < line.len() && char_class(line[ci]) == cls && cls != 0 {
ci += 1;
}
}
loop {
while ci < line.len() && char_class(line[ci]) == 0 {
ci += 1;
}
if ci < line.len() {
break;
}
if l < nlines() {
l += 1;
line = line_chars(l);
ci = 0;
} else {
ci = line.len();
break;
}
}
(l, ci)
};
let word_end = |l0: varnumber_T, ci0: usize| -> (varnumber_T, usize) {
let line = line_chars(l0);
let mut ci = ci0 + 1;
while ci < line.len() && char_class(line[ci]) == 0 {
ci += 1;
}
if ci < line.len() {
let cls = char_class(line[ci]);
while ci + 1 < line.len() && char_class(line[ci + 1]) == cls {
ci += 1;
}
}
(l0, ci.min(line.len().saturating_sub(1)))
};
let word_back = |l0: varnumber_T, ci0: usize| -> (varnumber_T, usize) {
let line = line_chars(l0);
let mut ci = ci0;
if ci == 0 {
return (l0, 0);
}
ci -= 1;
while ci > 0 && char_class(line[ci]) == 0 {
ci -= 1;
}
if ci < line.len() {
let cls = char_class(line[ci]);
while ci > 0 && char_class(line[ci - 1]) == cls {
ci -= 1;
}
}
(l0, ci)
};
let mut i = 0usize;
while i < k.len() {
let mut count: i64 = 0;
let mut has_count = false;
while i < k.len() && k[i].is_ascii_digit() && (k[i] != '0' || has_count) {
count = count * 10 + (k[i] as i64 - '0' as i64);
has_count = true;
i += 1;
}
if i >= k.len() {
break;
}
let cmd = k[i];
i += 1;
let n = if has_count { count.max(1) } else { 1 };
let (l, ci) = {
let (cl, cc) = cur();
(cl, (cc - 1).max(0) as usize)
};
match cmd {
'h' => set_cursorpos(l, (ci as varnumber_T + 1 - n).max(1)),
'l' => set_cursorpos(l, ci as varnumber_T + 1 + n),
'0' => set_cursorpos(l, 1),
'^' => {
let line = line_chars(l);
let f = line.iter().position(|c| !c.is_whitespace()).unwrap_or(0);
set_cursorpos(l, f as varnumber_T + 1);
}
'$' => {
let len = line_chars(l).len().max(1);
set_cursorpos(l, len as varnumber_T);
}
'j' => set_cursorpos((l + n).min(nlines()), ci as varnumber_T + 1),
'k' => set_cursorpos((l - n).max(1), ci as varnumber_T + 1),
'G' => set_cursorpos(if has_count { count } else { nlines() }, 1),
'|' => set_cursorpos(l, n),
'g' => {
if i < k.len() && k[i] == 'g' {
i += 1;
set_cursorpos(if has_count { count } else { 1 }, 1);
}
}
'w' => {
let (mut tl, mut tc) = (l, ci);
for _ in 0..n {
let (a, b) = word_fwd(tl, tc);
tl = a;
tc = b;
}
set_cursorpos(tl, tc as varnumber_T + 1);
}
'b' => {
let (mut tl, mut tc) = (l, ci);
for _ in 0..n {
let (a, b) = word_back(tl, tc);
tl = a;
tc = b;
}
set_cursorpos(tl, tc as varnumber_T + 1);
}
'e' => {
let (mut tl, mut tc) = (l, ci);
for _ in 0..n {
let (a, b) = word_end(tl, tc);
tl = a;
tc = b;
}
set_cursorpos(tl, tc as varnumber_T + 1);
}
'x' => {
let mut line = line_chars(l);
let end = (ci + n as usize).min(line.len());
if ci < line.len() {
let removed: String = line[ci..end].iter().collect();
write_reg_contents_lst('"', vec![removed], MotionType::CharWise, false);
line.drain(ci..end);
put_line(l, &line);
set_cursorpos(
l,
(ci as varnumber_T + 1).min(line.len().max(1) as varnumber_T),
);
}
}
'X' => {
let mut line = line_chars(l);
let start = ci.saturating_sub(n as usize);
if ci > 0 {
line.drain(start..ci);
put_line(l, &line);
set_cursorpos(l, start as varnumber_T + 1);
}
}
'D' | 'C' => {
let mut line = line_chars(l);
if ci < line.len() {
let removed: String = line[ci..].iter().collect();
write_reg_contents_lst('"', vec![removed], MotionType::CharWise, false);
line.truncate(ci);
put_line(l, &line);
}
}
'd' | 'c' | 'y' => {
let op = cmd;
let m = if i < k.len() { k[i] } else { ' ' };
i += 1;
if m == op || (op == 'c' && m == 'c') {
let last = (l + n - 1).min(nlines());
let lines = get_buffer_lines(l, last);
write_reg_contents_lst('"', lines, MotionType::LineWise, false);
if op != 'y' {
CURBUF.with(|b| {
let mut b = b.borrow_mut();
let (a, z) = ((l - 1) as usize, (last as usize).min(b.len()));
b.drain(a..z);
if b.is_empty() {
b.push(String::new());
}
});
set_cursorpos(l, 1);
}
} else {
let line = line_chars(l);
let target = match m {
'$' => line.len(),
'0' => 0,
'w' => word_fwd(l, ci).1.min(line.len()),
'e' => word_end(l, ci).1 + 1,
'l' => (ci + 1).min(line.len()),
'h' => ci.saturating_sub(1),
_ => ci,
};
let (a, z) = (ci.min(target), ci.max(target));
let removed: String = line[a..z.min(line.len())].iter().collect();
write_reg_contents_lst('"', vec![removed], MotionType::CharWise, false);
if op != 'y' {
let mut nl = line.clone();
nl.drain(a..z.min(nl.len()));
put_line(l, &nl);
set_cursorpos(l, a as varnumber_T + 1);
}
}
}
'Y' => {
let last = (l + n - 1).min(nlines());
write_reg_contents_lst('"', get_buffer_lines(l, last), MotionType::LineWise, false);
}
'p' | 'P' => {
let (mtype, _) = get_reg_type('"');
if let Some(reg) = get_reg_contents('"') {
if mtype == MotionType::LineWise {
let at = if cmd == 'p' { l } else { l - 1 };
set_buffer_lines(at, reg.clone(), true);
set_cursorpos(at + 1, 1);
} else {
let mut line = line_chars(l);
let at = if cmd == 'p' {
(ci + 1).min(line.len())
} else {
ci
};
let text: Vec<char> = reg.join("").chars().collect();
for (j, c) in text.iter().enumerate() {
line.insert(at + j, *c);
}
put_line(l, &line);
set_cursorpos(l, (at + text.len()) as varnumber_T);
}
}
}
'J' => {
let last = (l + n.max(2) - 1).min(nlines());
ex_join(l, last, false);
}
'r' => {
if i < k.len() {
let rc = k[i];
i += 1;
let mut line = line_chars(l);
for j in 0..n as usize {
if ci + j < line.len() {
line[ci + j] = rc;
}
}
put_line(l, &line);
}
}
'~' => {
let mut line = line_chars(l);
for j in 0..n as usize {
if ci + j < line.len() {
let c = line[ci + j];
line[ci + j] = if c.is_uppercase() {
c.to_ascii_lowercase()
} else {
c.to_ascii_uppercase()
};
}
}
put_line(l, &line);
set_cursorpos(l, (ci + n as usize).min(line.len().max(1)) as varnumber_T);
}
_ => {}
}
}
}
thread_local! {
static CMDLINE: std::cell::RefCell<(String, varnumber_T, String)> =
const { std::cell::RefCell::new((String::new(), 0, String::new())) };
}
pub fn f_setcmdline(argvars: &[typval_T], rettv: &mut typval_T) {
let line = tv_get_string(&argvars[0]);
let pos = if argvars.len() >= 2 {
tv_get_number(&argvars[1])
} else {
line.len() as varnumber_T + 1
};
CMDLINE.with(|c| {
let mut c = c.borrow_mut();
c.0 = line;
c.1 = pos;
});
rettv.vval = v_number(0);
}
pub fn f_getcmdline(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(CMDLINE.with(|c| c.borrow().0.clone()));
}
pub fn f_setcmdpos(argvars: &[typval_T], rettv: &mut typval_T) {
let pos = tv_get_number(&argvars[0]);
CMDLINE.with(|c| c.borrow_mut().1 = pos);
rettv.vval = v_number(0);
}
pub fn f_getcmdpos(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(CMDLINE.with(|c| c.borrow().1));
}
pub fn f_getcmdtype(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(CMDLINE.with(|c| c.borrow().2.clone()));
}
thread_local! {
static SIGNS_PLACED: std::cell::RefCell<Vec<typval_T>> = const { std::cell::RefCell::new(Vec::new()) };
static SIGN_LAST_ID: std::cell::Cell<varnumber_T> = const { std::cell::Cell::new(0) };
}
fn sign_place_one(
id: varnumber_T,
group: &str,
name: &str,
bufnr: varnumber_T,
lnum: varnumber_T,
priority: varnumber_T,
) -> varnumber_T {
let id = if id == 0 {
SIGN_LAST_ID.with(|c| {
let v = c.get() + 1;
c.set(v);
v
})
} else {
id
};
let d = tv_dict_alloc();
{
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "id", id);
tv_dict_add_str(&mut db, "group", group);
tv_dict_add_str(&mut db, "name", name);
tv_dict_add_nr(&mut db, "bufnr", bufnr);
tv_dict_add_nr(&mut db, "lnum", lnum);
tv_dict_add_nr(&mut db, "priority", priority);
}
SIGNS_PLACED.with(|s| s.borrow_mut().push(match_dict_val(d)));
id
}
fn sign_dict_nr(d: &typval_T, key: &str, def: varnumber_T) -> varnumber_T {
match (d.v_type, &d.vval) {
(VAR_DICT, v_dict(Some(dd))) => tv_dict_find(&dd.borrow(), key)
.map(tv_get_number)
.unwrap_or(def),
_ => def,
}
}
fn sign_dict_str(d: &typval_T, key: &str, def: &str) -> String {
match (d.v_type, &d.vval) {
(VAR_DICT, v_dict(Some(dd))) => tv_dict_find(&dd.borrow(), key)
.map(tv_get_string)
.unwrap_or_else(|| def.to_string()),
_ => def.to_string(),
}
}
pub fn f_sign_place(argvars: &[typval_T], rettv: &mut typval_T) {
let id = tv_get_number(&argvars[0]);
let group = tv_get_string(&argvars[1]);
let name = tv_get_string(&argvars[2]);
let bufnr = if argvars[3].v_type == VAR_NUMBER {
tv_get_number(&argvars[3])
} else {
1
};
let dict = argvars.get(4);
let lnum = dict.map_or(1, |d| sign_dict_nr(d, "lnum", 1));
let priority = dict.map_or(10, |d| sign_dict_nr(d, "priority", 10));
rettv.vval = v_number(sign_place_one(id, &group, &name, bufnr, lnum, priority));
}
pub fn f_sign_placelist(argvars: &[typval_T], rettv: &mut typval_T) {
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
_ => {
emsg("E714: List required");
return;
}
};
let items: Vec<typval_T> = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
let out = tv_list_alloc_ret(rettv, items.len() as isize);
let mut ob = out.borrow_mut();
for it in items {
if it.v_type != VAR_DICT {
tv_list_append_number(&mut ob, -1);
continue;
}
let id = sign_dict_nr(&it, "id", 0);
let group = sign_dict_str(&it, "group", "");
let name = sign_dict_str(&it, "name", "");
let bufnr = sign_dict_nr(&it, "buffer", 1);
let lnum = sign_dict_nr(&it, "lnum", 1);
let priority = sign_dict_nr(&it, "priority", 10);
tv_list_append_number(
&mut ob,
sign_place_one(id, &group, &name, bufnr, lnum, priority),
);
}
}
pub fn f_sign_getplaced(argvars: &[typval_T], rettv: &mut typval_T) {
let want_buf = argvars
.first()
.filter(|t| t.v_type == VAR_NUMBER)
.map(tv_get_number);
let dict = argvars.get(1);
let want_group = dict.and_then(|d| match (d.v_type, &d.vval) {
(VAR_DICT, v_dict(Some(dd))) => tv_dict_find(&dd.borrow(), "group").map(tv_get_string),
_ => None,
});
let placed: Vec<typval_T> = SIGNS_PLACED.with(|s| s.borrow().clone());
let mut buffers: Vec<varnumber_T> = Vec::new();
let mut grouped: std::collections::BTreeMap<varnumber_T, Vec<typval_T>> =
std::collections::BTreeMap::new();
for sg in placed {
let bufnr = sign_dict_nr(&sg, "bufnr", 0);
if let Some(wb) = want_buf {
if wb != bufnr {
continue;
}
}
if let Some(wg) = &want_group {
if *wg != sign_dict_str(&sg, "group", "") {
continue;
}
}
if !buffers.contains(&bufnr) {
buffers.push(bufnr);
}
grouped.entry(bufnr).or_default().push(sg);
}
let out = tv_list_alloc_ret(rettv, buffers.len() as isize);
let mut ob = out.borrow_mut();
for bufnr in buffers {
let signs = grouped.remove(&bufnr).unwrap_or_default();
let entry = tv_dict_alloc();
{
let mut eb = entry.borrow_mut();
tv_dict_add_nr(&mut eb, "bufnr", bufnr);
let sl = tv_list_alloc(signs.len() as isize);
{
let mut slb = sl.borrow_mut();
for sg in signs {
tv_list_append_tv(&mut slb, sg);
}
}
tv_dict_add_tv(
&mut eb,
"signs",
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(sl)),
},
);
}
tv_list_append_tv(&mut ob, match_dict_val(entry));
}
}
pub fn f_sign_unplace(argvars: &[typval_T], rettv: &mut typval_T) {
let group = tv_get_string(&argvars[0]);
let dict = argvars.get(1);
let want_id = dict.map(|d| sign_dict_nr(d, "id", 0)).filter(|v| *v != 0);
let want_buf = dict
.map(|d| sign_dict_nr(d, "buffer", -1))
.filter(|v| *v != -1);
SIGNS_PLACED.with(|s| {
s.borrow_mut().retain(|sg| {
let g_ok = group.is_empty() || group == "*" || group == sign_dict_str(sg, "group", "");
#[allow(clippy::unnecessary_map_or)]
let id_ok = want_id.map_or(true, |w| w == sign_dict_nr(sg, "id", 0));
#[allow(clippy::unnecessary_map_or)]
let buf_ok = want_buf.map_or(true, |w| w == sign_dict_nr(sg, "bufnr", 0));
!(g_ok && id_ok && buf_ok)
});
});
rettv.vval = v_number(0);
}
pub fn f_sign_unplacelist(argvars: &[typval_T], rettv: &mut typval_T) {
let l = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l.clone(),
_ => {
emsg("E714: List required");
return;
}
};
let items: Vec<typval_T> = l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
let out = tv_list_alloc_ret(rettv, items.len() as isize);
let mut ob = out.borrow_mut();
for it in items {
let group = sign_dict_str(&it, "group", "");
let id = sign_dict_nr(&it, "id", 0);
SIGNS_PLACED.with(|s| {
s.borrow_mut().retain(|sg| {
let g_ok = group.is_empty() || group == sign_dict_str(sg, "group", "");
let id_ok = id == 0 || id == sign_dict_nr(sg, "id", 0);
!(g_ok && id_ok)
});
});
tv_list_append_number(&mut ob, 0);
}
}
pub fn f_sign_jump(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
}
pub fn f_indent(argvars: &[typval_T], rettv: &mut typval_T) {
let lnum = tv_get_lnum(&argvars[0]);
if lnum < 1 || lnum > curbuf_len() {
rettv.vval = v_number(-1);
return;
}
let line = get_buffer_lines(lnum, lnum)
.into_iter()
.next()
.unwrap_or_default();
let ts = {
let t = tv_get_number_chk(&get_option_value("tabstop"), None);
if t > 0 {
t as varnumber_T
} else {
8
}
};
let mut col: varnumber_T = 0;
for c in line.chars() {
match c {
' ' => col += 1,
'\t' => col += ts - (col % ts),
_ => break,
}
}
rettv.vval = v_number(col);
}
pub fn f_foldtext(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_foldtextresult(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_highlight_exists(argvars: &[typval_T], rettv: &mut typval_T) {
let name = argvars.first().map(tv_get_string).unwrap_or_default();
rettv.vval = v_number(hl_exists(&name) as varnumber_T);
}
pub fn f_diff_filler(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_hlID(argvars: &[typval_T], rettv: &mut typval_T) {
let name = argvars.first().map(tv_get_string).unwrap_or_default();
rettv.vval = v_number(hl_id(&name));
}
pub fn f_diff_hlID(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_virtcol2col(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
}
pub fn f_wildtrigger(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn f_searchcount(argvars: &[typval_T], rettv: &mut typval_T) {
let mut pat = LAST_SEARCH.with(|p| p.borrow().clone());
let mut maxcount: varnumber_T = 99;
if let Some((VAR_DICT, v_dict(Some(opt)))) = argvars.first().map(|t| (t.v_type, &t.vval)) {
let opt = opt.borrow();
if let Some(p) = tv_dict_find(&opt, "pattern") {
pat = tv_get_string(p);
}
if let Some(m) = tv_dict_find(&opt, "maxcount") {
maxcount = tv_get_number(m);
}
}
let (mut total, mut current, mut exact) = (0i64, 0i64, 0i64);
if !pat.is_empty() {
let ic = tv_get_number_chk(&get_option_value("ignorecase"), None) != 0;
let (clnum, ccol) = CURPOS.with(|c| {
let c = c.borrow();
(c.0, c.1)
});
for (i, line) in get_buffer_lines(1, curbuf_len()).iter().enumerate() {
let lnum = i as varnumber_T + 1;
let mut from = 0usize;
while let Some((s, e)) = line_match_from(&pat, line, from, ic) {
total += 1;
if lnum < clnum || (lnum == clnum && (s as varnumber_T) < ccol) {
current = total;
}
if lnum == clnum && (s as varnumber_T) == ccol - 1 {
exact = 1;
current = total;
}
from = if e > s { e } else { s + 1 };
}
}
}
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "current", current);
tv_dict_add_nr(&mut db, "total", total);
tv_dict_add_nr(&mut db, "exact_match", exact);
tv_dict_add_nr(&mut db, "incomplete", 0);
tv_dict_add_nr(&mut db, "maxcount", maxcount);
}
pub fn f_complete_info(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "mode", "");
tv_dict_add_nr(&mut db, "pum_visible", 0);
let items = tv_list_alloc(0);
tv_dict_add_tv(
&mut db,
"items",
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(items)),
},
);
tv_dict_add_nr(&mut db, "selected", -1);
}
thread_local! {
static QFLIST: std::cell::RefCell<(Vec<typval_T>, String)> =
const { std::cell::RefCell::new((Vec::new(), String::new())) };
static LOCLIST: std::cell::RefCell<(Vec<typval_T>, String)> =
const { std::cell::RefCell::new((Vec::new(), String::new())) };
}
fn qf_add_entry(item: &typval_T) -> typval_T {
let d = tv_dict_alloc();
let bufnr = sign_dict_nr(item, "bufnr", 0);
let lnum = sign_dict_nr(item, "lnum", 0);
{
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "bufnr", bufnr);
tv_dict_add_str(&mut db, "module", &sign_dict_str(item, "module", ""));
tv_dict_add_nr(&mut db, "lnum", lnum);
tv_dict_add_nr(&mut db, "end_lnum", sign_dict_nr(item, "end_lnum", 0));
tv_dict_add_nr(&mut db, "col", sign_dict_nr(item, "col", 0));
tv_dict_add_nr(&mut db, "end_col", sign_dict_nr(item, "end_col", 0));
tv_dict_add_nr(&mut db, "vcol", sign_dict_nr(item, "vcol", 0));
tv_dict_add_nr(&mut db, "nr", sign_dict_nr(item, "nr", 0));
tv_dict_add_str(&mut db, "pattern", &sign_dict_str(item, "pattern", ""));
tv_dict_add_str(&mut db, "text", &sign_dict_str(item, "text", ""));
tv_dict_add_str(&mut db, "type", &sign_dict_str(item, "type", ""));
let valid = if bufnr > 0 || lnum > 0 { 1 } else { 0 };
tv_dict_add_nr(&mut db, "valid", valid);
}
match_dict_val(d)
}
fn qf_set_list(
store: &std::cell::RefCell<(Vec<typval_T>, String)>,
list: &typval_T,
action: &str,
what: Option<&typval_T>,
) -> varnumber_T {
let mut s = store.borrow_mut();
if action == "f" {
s.0.clear();
s.1.clear();
return 0;
}
let entries: Vec<typval_T> = match (list.v_type, &list.vval) {
(VAR_LIST, v_list(Some(l))) => l
.borrow()
.lv_items
.iter()
.filter(|it| it.li_tv.v_type == VAR_DICT)
.map(|it| qf_add_entry(&it.li_tv))
.collect(),
_ => {
emsg("E714: List required");
return -1;
}
};
if action == "a" {
s.0.extend(entries);
} else {
s.0 = entries;
}
if let Some(w) = what {
if let (VAR_DICT, v_dict(Some(wd))) = (w.v_type, &w.vval) {
if let Some(t) = tv_dict_find(&wd.borrow(), "title") {
s.1 = tv_get_string(t);
}
}
}
0
}
fn qf_get_list(
store: &std::cell::RefCell<(Vec<typval_T>, String)>,
what: Option<&typval_T>,
rettv: &mut typval_T,
) {
let s = store.borrow();
match what {
Some(w) if w.v_type == VAR_DICT => {
let keys: Vec<String> = match &w.vval {
v_dict(Some(wd)) => wd.borrow().dv_hashtab.keys().cloned().collect(),
_ => Vec::new(),
};
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
for k in keys {
match k.as_str() {
"title" => {
tv_dict_add_str(&mut db, "title", &s.1);
}
"nr" => {
tv_dict_add_nr(&mut db, "nr", 0);
}
"size" => {
tv_dict_add_nr(&mut db, "size", s.0.len() as varnumber_T);
}
"winid" => {
tv_dict_add_nr(&mut db, "winid", 0);
}
"items" => {
let l = tv_list_alloc(s.0.len() as isize);
{
let mut lb = l.borrow_mut();
for e in &s.0 {
tv_list_append_tv(&mut lb, e.clone());
}
}
tv_dict_add_tv(
&mut db,
"items",
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
},
);
}
_ => {}
}
}
}
_ => {
let l = tv_list_alloc_ret(rettv, s.0.len() as isize);
let mut lb = l.borrow_mut();
for e in &s.0 {
tv_list_append_tv(&mut lb, e.clone());
}
}
}
}
pub fn f_setqflist(argvars: &[typval_T], rettv: &mut typval_T) {
let action = if argvars.len() >= 2 {
tv_get_string(&argvars[1])
} else {
String::new()
};
rettv.vval = v_number(QFLIST.with(|q| qf_set_list(q, &argvars[0], &action, argvars.get(2))));
}
pub fn f_getqflist(argvars: &[typval_T], rettv: &mut typval_T) {
QFLIST.with(|q| qf_get_list(q, argvars.first(), rettv));
}
pub fn f_setloclist(argvars: &[typval_T], rettv: &mut typval_T) {
let action = if argvars.len() >= 3 {
tv_get_string(&argvars[2])
} else {
String::new()
};
rettv.vval = v_number(LOCLIST.with(|q| qf_set_list(q, &argvars[1], &action, argvars.get(3))));
}
pub fn f_getloclist(argvars: &[typval_T], rettv: &mut typval_T) {
LOCLIST.with(|q| qf_get_list(q, argvars.get(1), rettv));
}
pub fn f_getcompletion(argvars: &[typval_T], rettv: &mut typval_T) {
let pat = tv_get_string(&argvars[0]);
let typ = tv_get_string(&argvars[1]);
let mut results: Vec<String> = match typ.as_str() {
"environment" => std::env::vars()
.map(|(k, _)| k)
.filter(|k| k.starts_with(&pat))
.collect(),
"dir" | "file" | "file_in_path" | "buffer" => {
let (dir, leaf) = match pat.rfind('/') {
Some(i) => (&pat[..=i], &pat[i + 1..]),
None => ("", pat.as_str()),
};
let readdir = if dir.is_empty() { "." } else { dir };
let mut out = Vec::new();
if let Ok(rd) = std::fs::read_dir(readdir) {
for e in rd.flatten() {
let name = e.file_name().to_string_lossy().into_owned();
if !name.starts_with(leaf) {
continue;
}
let is_dir = e.path().is_dir();
if typ == "dir" && !is_dir {
continue;
}
let mut full = format!("{dir}{name}");
if is_dir {
full.push('/');
}
out.push(full);
}
}
out
}
_ => Vec::new(),
};
results.sort();
let l = tv_list_alloc_ret(rettv, results.len() as isize);
let mut lb = l.borrow_mut();
for r in results {
tv_list_append_string(&mut lb, &r);
}
}
pub fn f_getchar(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_getcharstr(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_getcharmod(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_getcmdprompt(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_getcmdscreenpos(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_getcmdcompltype(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_getcmdcomplpat(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_cindent(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
}
pub fn f_lispindent(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(-1);
}
pub fn f_complete_add(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_complete_check(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.vval = v_number(0);
}
pub fn f_cmdcomplete_info(_argvars: &[typval_T], rettv: &mut typval_T) {
let _ = tv_dict_alloc_ret(rettv);
}
pub fn f_menu_info(_argvars: &[typval_T], rettv: &mut typval_T) {
let _ = tv_dict_alloc_ret(rettv);
}
pub fn f_test_garbagecollect_now(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn f_test_write_list_log(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn f_pyeval(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
pub fn f_pyxeval(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_SPECIAL;
rettv.vval = v_special(kSpecialVarNull);
}
pub fn f_undofile(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let name = tv_get_string(&argvars[0]);
if name.is_empty() {
rettv.vval = v_string(String::new());
return;
}
let p = std::path::Path::new(&name);
let fname = p
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
let undoname = format!(".{fname}.un~");
let result = match p.parent() {
Some(par) if !par.as_os_str().is_empty() => {
par.join(&undoname).to_string_lossy().into_owned()
}
_ => undoname,
};
rettv.vval = v_string(result);
}
pub fn f_undotree(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
tv_dict_add_nr(&mut db, "seq_last", 0);
tv_dict_add_nr(&mut db, "seq_cur", 0);
tv_dict_add_nr(&mut db, "time_cur", 0);
tv_dict_add_nr(&mut db, "save_last", 0);
tv_dict_add_nr(&mut db, "save_cur", 0);
tv_dict_add_nr(&mut db, "synced", 1);
let entries = tv_list_alloc(0);
tv_dict_add_tv(
&mut db,
"entries",
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(entries)),
},
);
}
pub fn f_getmousepos(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
for k in [
"screenrow",
"screencol",
"winid",
"winrow",
"wincol",
"line",
"column",
"coladd",
] {
tv_dict_add_nr(&mut db, k, 0);
}
}
pub fn f_screenpos(_argvars: &[typval_T], rettv: &mut typval_T) {
let d = tv_dict_alloc_ret(rettv);
let mut db = d.borrow_mut();
for k in ["row", "col", "curscol", "endcol"] {
tv_dict_add_nr(&mut db, k, 0);
}
}
pub fn f_getcompletiontype(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_mapset(argvars: &[typval_T], _rettv: &mut typval_T) {
let (mode_str, dict) = if argvars[0].v_type == VAR_DICT {
(None, &argvars[0])
} else {
match argvars.get(2) {
Some(d) => (Some(tv_get_string(&argvars[0])), d),
None => return,
}
};
let d = match (dict.v_type, &dict.vval) {
(VAR_DICT, v_dict(Some(d))) => d.clone(),
_ => {
emsg("E715: Dictionary required");
return;
}
};
let db = d.borrow();
let get_s = |k: &str| tv_dict_find(&db, k).map(tv_get_string).unwrap_or_default();
let get_n = |k: &str| tv_dict_find(&db, k).map(tv_get_number).unwrap_or(0);
let mode = match mode_str {
Some(m) => mode_str2flags(&m),
None => {
let mc = get_s("mode");
if !mc.is_empty() {
mode_str2flags(&mc)
} else if tv_dict_find(&db, "mode_bits").is_some() {
get_n("mode_bits") as i32
} else {
MAP_DEFAULT
}
}
};
let lhs = get_s("lhs");
if lhs.is_empty() {
return;
}
map_add(mapblock_T {
lhs,
rhs: get_s("rhs"),
mode,
noremap: get_n("noremap") != 0,
expr: get_n("expr") != 0,
silent: get_n("silent") != 0,
nowait: get_n("nowait") != 0,
buffer: get_n("buffer") != 0,
sid: get_n("sid"),
lnum: get_n("lnum"),
desc: get_s("desc"),
});
}
pub fn f_complete(_argvars: &[typval_T], _rettv: &mut typval_T) {}
pub fn f_preinserted(_argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
rettv.vval = v_string(String::new());
}
pub fn f_getscriptinfo(_argvars: &[typval_T], rettv: &mut typval_T) {
let _ = tv_list_alloc_ret(rettv, 0);
}
pub fn f_getstacktrace(_argvars: &[typval_T], rettv: &mut typval_T) {
let _ = tv_list_alloc_ret(rettv, 0);
}
pub fn f_fullcommand(argvars: &[typval_T], rettv: &mut typval_T) {
rettv.v_type = VAR_STRING;
let mut name = tv_get_string(&argvars[0]);
name = name
.trim_start_matches([':', ' '])
.trim_end_matches('!')
.to_string();
const CMDS: &[(&str, &str)] = &[
("printf", "printf"),
("e", "edit"),
("ec", "echo"),
("echom", "echomsg"),
("w", "write"),
("wq", "wq"),
("q", "quit"),
("qa", "quitall"),
("s", "substitute"),
("sp", "split"),
("se", "set"),
("so", "source"),
("g", "global"),
("norm", "normal"),
("vs", "vsplit"),
("b", "buffer"),
("bn", "bnext"),
("bp", "bprevious"),
("d", "delete"),
("y", "yank"),
("pu", "put"),
("co", "copy"),
("m", "move"),
("r", "read"),
("let", "let"),
("cal", "call"),
("fu", "function"),
("retu", "return"),
("if", "if"),
("for", "for"),
("wh", "while"),
("try", "try"),
("au", "autocmd"),
("com", "command"),
];
let result = if name.is_empty() {
String::new()
} else {
CMDS.iter()
.find(|(min, full)| name.starts_with(min) && full.starts_with(&name))
.map(|(_, full)| full.to_string())
.unwrap_or_default()
};
rettv.vval = v_string(result);
}
#[allow(unused_imports)]
use crate::ported::eval::typval::{
tv_check_for_dict_arg, tv_check_for_list_arg, tv_check_for_opt_dict_arg,
tv_check_for_opt_number_arg, tv_check_for_string_or_list_arg, tv_copy,
tv_dict_add_allocated_str, tv_dict_extend, tv_dict_get_bool, tv_dict_get_string,
tv_dict_item_remove, tv_list_append_allocated_string, tv_list_append_owned_tv,
};
const SP_NOMOVE: i32 = 0x01; const SP_REPEAT: i32 = 0x02; const SP_RETCOUNT: i32 = 0x04; const SP_SETPCMARK: i32 = 0x08; const SP_START: i32 = 0x10; const SP_SUBPAT: i32 = 0x20; const SP_END: i32 = 0x40; const SP_COLUMN: i32 = 0x80;
const MAXCOL: crate::ported::window::colnr_T = crate::ported::window::colnr_T::MAX;
const MAX_FUNC_ARGS: i32 = 20;
pub fn f_call(argvars: &[typval_T], rettv: &mut typval_T) {
if tv_check_for_list_arg(argvars, 1) == FAIL {
return;
}
let arglist_null = !matches!(&argvars[1].vval, v_list(Some(_)));
if arglist_null {
return;
}
let mut partial: Option<std::rc::Rc<crate::ported::eval::typval_defs_h::partial_T>> = None;
let mut func: String = match (&argvars[0].v_type, &argvars[0].vval) {
(VAR_FUNC, v_string(s)) => s.clone(),
(VAR_PARTIAL, v_partial(Some(pt))) => {
partial = Some(pt.clone());
crate::ported::eval::partial_name(pt).to_string()
}
_ => tv_get_string(&argvars[0]),
};
if func.is_empty() {
return;
}
if argvars[0].v_type == VAR_STRING {
use crate::ported::eval::userfunc::tfn::{TFN_INT, TFN_QUIET};
let mut p: &str = &func;
match crate::ported::eval::userfunc::trans_function_name(
&mut p,
false,
TFN_INT | TFN_QUIET,
None,
None,
) {
None => {
crate::ported::eval::userfunc::emsg_funcname("E117: Unknown function: %s", &func);
return;
}
Some(tofree) => func = tofree,
}
}
let mut selfdict: Option<
std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>,
> = None;
if argvars[2].v_type != VAR_UNKNOWN {
if tv_check_for_dict_arg(argvars, 2) == FAIL {
return;
}
if let v_dict(Some(d)) = &argvars[2].vval {
selfdict = Some(d.clone());
}
}
crate::ported::eval::userfunc::func_call(
&func,
&argvars[1],
partial.as_ref(),
selfdict.as_ref(),
rettv,
);
}
pub fn f_eval(argvars: &[typval_T], rettv: &mut typval_T) {
let s0 = tv_get_string_chk(&argvars[0]);
let expr = s0
.as_deref()
.map(|s| crate::ported::eval::skipwhite(s).to_string());
let mut ok = false;
let mut trailing: Option<String> = None;
if let Some(e) = &expr {
let mut p: &str = e;
let mut ea = crate::ported::eval::evalarg_T {
eval_flags: crate::ported::eval::EVAL_EVALUATE,
};
if crate::ported::eval::eval1(&mut p, rettv, Some(&mut ea)) != FAIL {
ok = true;
if !p.is_empty() {
trailing = Some(p.to_string());
}
}
}
if !ok {
if let Some(e) = &expr {
crate::ported::message::semsg(&format!("E15: Invalid expression: \"{e}\""));
}
rettv.v_type = VAR_NUMBER;
rettv.vval = v_number(0);
} else if let Some(t) = trailing {
crate::ported::message::semsg(&format!("E488: Trailing characters: {t}"));
}
}
pub fn common_function(argvars: &[typval_T], rettv: &mut typval_T, is_funcref: bool) {
use crate::ported::eval::typval_defs_h::{partial_T, VarLockStatus};
use crate::ported::eval::userfunc::tfn::{TFN_INT, TFN_NO_AUTOLOAD, TFN_NO_DEREF, TFN_QUIET};
let mut use_string = false; let mut arg_pt: Option<std::rc::Rc<partial_T>> = None; let mut trans_name: Option<String> = None;
let mut s: Option<String> = match (&argvars[0].v_type, &argvars[0].vval) {
(VAR_FUNC, v_string(name)) => Some(name.clone()), (VAR_PARTIAL, v_partial(Some(pt))) => {
arg_pt = Some(pt.clone()); Some(crate::ported::eval::partial_name(pt).to_string())
}
_ => {
use_string = true; Some(tv_get_string(&argvars[0]))
}
};
let has_autoload = s
.as_deref()
.is_some_and(|x| x.as_bytes().contains(&crate::ported::eval::AUTOLOAD_CHAR));
if (use_string && !has_autoload) || is_funcref {
let orig = s.clone().unwrap_or_default();
let mut name: &str = &orig;
trans_name = crate::ported::eval::userfunc::save_function_name(
&mut name,
false,
TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF,
None,
);
if !name.is_empty() {
s = None;
}
}
let s_empty = s.as_deref().is_none_or(str::is_empty);
let s_digit = s
.as_deref()
.and_then(|x| x.bytes().next())
.is_some_and(|b| b.is_ascii_digit());
if s_empty || (use_string && s_digit) || (is_funcref && trans_name.is_none()) {
let arg = if use_string {
tv_get_string(&argvars[0])
} else {
s.clone().unwrap_or_default()
};
crate::ported::message::semsg(&format!("E475: Invalid argument: {arg}"));
return;
}
let name_exists = trans_name.as_deref().is_some_and(|tn| {
if is_funcref {
crate::ported::eval::userfunc::find_func(tn).is_some()
} else {
crate::ported::eval::userfunc::translated_function_exists(tn)
}
});
if trans_name.is_some() && !name_exists {
crate::ported::message::semsg(&format!(
"E700: Unknown function: {}",
s.clone().unwrap_or_default()
));
return;
}
let sname = s.unwrap_or_default();
let name: String = if sname.starts_with("s:") || sname.starts_with("<SID>") {
crate::ported::eval::userfunc::get_scriptlocal_funcname(Some(&sname)).unwrap_or_default()
} else {
sname.clone()
};
let mut dict_idx = 0usize;
let mut arg_idx = 0usize;
let mut list: Option<
std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::list_T>>,
> = None;
if argvars[1].v_type != VAR_UNKNOWN {
if argvars[2].v_type != VAR_UNKNOWN {
arg_idx = 1; dict_idx = 2;
} else if argvars[1].v_type == VAR_DICT {
dict_idx = 1; } else {
arg_idx = 1; }
if dict_idx > 0 {
if tv_check_for_dict_arg(argvars, dict_idx) == FAIL {
return;
}
if !matches!(&argvars[dict_idx].vval, v_dict(Some(_))) {
dict_idx = 0; }
}
if arg_idx > 0 {
match &argvars[arg_idx].vval {
v_list(Some(l)) if argvars[arg_idx].v_type == VAR_LIST => {
let len = tv_list_len(&l.borrow());
if len == 0 {
arg_idx = 0; } else if len > MAX_FUNC_ARGS {
crate::ported::eval::userfunc::emsg_funcname(
"E118: Too many arguments for function: %s",
&sname,
);
return;
} else {
list = Some(l.clone());
}
}
_ => {
crate::ported::message::emsg(
"E923: Second argument of function() must be a list or a dict",
);
return;
}
}
}
}
let make_partial = dict_idx > 0 || arg_idx > 0 || arg_pt.is_some() || is_funcref;
if make_partial {
let mut pt = partial_T {
pt_refcount: 1, pt_name: String::new(),
pt_argv: Vec::new(),
pt_dict: None,
};
let arg_len = arg_pt.as_ref().map_or(0, |p| p.pt_argv.len());
let lv_len = list
.as_ref()
.map_or(0, |l| tv_list_len(&l.borrow()) as usize);
if arg_idx > 0 || arg_len > 0 {
if let Some(ap) = &arg_pt {
for i in 0..arg_len {
let mut dst = typval_T::default();
tv_copy(&ap.pt_argv[i], &mut dst); pt.pt_argv.push(dst);
}
}
if lv_len > 0 {
if let Some(l) = &list {
for it in &l.borrow().lv_items {
let mut dst = typval_T::default();
tv_copy(&it.li_tv, &mut dst); pt.pt_argv.push(dst);
}
}
}
}
if dict_idx > 0 {
if let v_dict(Some(d)) = &argvars[dict_idx].vval {
d.borrow_mut().dv_refcount += 1; pt.pt_dict = Some(d.clone());
}
} else if let Some(ap) = &arg_pt {
if let Some(d) = &ap.pt_dict {
d.borrow_mut().dv_refcount += 1; pt.pt_dict = Some(d.clone());
}
}
pt.pt_name = match &arg_pt {
Some(ap) if !ap.pt_name.is_empty() => ap.pt_name.clone(),
_ => name,
};
crate::ported::eval::userfunc::func_ref();
rettv.v_type = VAR_PARTIAL;
rettv.v_lock = VarLockStatus::VAR_UNLOCKED;
rettv.vval = v_partial(Some(std::rc::Rc::new(pt)));
} else {
crate::ported::eval::userfunc::func_ref();
rettv.v_type = VAR_FUNC;
rettv.v_lock = VarLockStatus::VAR_UNLOCKED;
rettv.vval = v_string(name);
}
}
pub fn get_col(argvars: &[typval_T], rettv: &mut typval_T, charcol: bool) {
if tv_check_for_string_or_list_arg(argvars, 0) == FAIL
|| tv_check_for_opt_number_arg(argvars, 1) == FAIL
{
return;
}
let mut wp = crate::ported::window::curwin.with(|c| c.borrow().clone());
if argvars[1].v_type != VAR_UNKNOWN {
let mut tp = None;
wp = crate::ported::eval::window::win_id2wp_tp(
tv_get_number(&argvars[1]) as i32,
Some(&mut tp),
);
if wp.is_none() || tp.is_none() {
return; }
}
let wp = match wp {
Some(w) => w,
None => return,
};
let bp = match wp.borrow().w_buffer.clone() {
Some(b) => b,
None => return,
};
let mut col: crate::ported::window::colnr_T = 0; let mut fnum = bp.borrow().handle; let fp = crate::ported::eval::var2fpos(&argvars[0], false, &mut fnum, charcol, &wp);
if let Some(fp) = fp {
if fnum == bp.borrow().handle {
if fp.col == MAXCOL {
if fp.lnum <= bp.borrow().b_ml.ml_line_count {
col = crate::ported::buffer::ml_get_buf_len(&mut bp.borrow_mut(), fp.lnum) + 1;
} else {
col = MAXCOL;
}
} else {
col = fp.col + 1; if virtual_active() && wp.borrow().w_cursor == fp {
let line = crate::ported::buffer::ml_get_buf(
&mut bp.borrow_mut(),
wp.borrow().w_cursor.lnum,
);
let cur_col = wp.borrow().w_cursor.col;
let p = line.get(cur_col as usize..).unwrap_or("");
let _ = crate::ported::plines::win_chartabsize(&wp, p, 0);
}
}
}
}
rettv.vval = v_number(col as varnumber_T); }
fn virtual_active() -> bool {
false
}
pub fn set_position(argvars: &[typval_T], rettv: &mut typval_T, charpos: bool) {
let mut curswant: crate::ported::window::colnr_T = -1;
rettv.vval = v_number(-1); let name = match tv_get_string_chk(&argvars[0]) {
Some(n) => n,
None => return, };
let mut pos = crate::ported::window::pos_T::default();
let mut fnum = 0i32;
if crate::ported::eval::list2fpos(
&argvars[1],
&mut pos,
Some(&mut fnum),
Some(&mut curswant),
charpos,
) != OK
{
return; }
if pos.col != MAXCOL {
pos.col -= 1;
if pos.col < 0 {
pos.col = 0;
}
}
let nb = name.as_bytes();
if nb == b"." {
if let Some(w) = crate::ported::window::curwin.with(|c| c.borrow().clone()) {
w.borrow_mut().w_cursor = pos;
if curswant >= 0 {
}
}
rettv.vval = v_number(0); } else if nb.len() == 2 && nb[0] == b'\'' {
if crate::ported::mark::setmark_pos(nb[1] as i32, &pos, fnum) == OK {
rettv.vval = v_number(0); }
} else {
crate::ported::message::emsg("E474: Invalid argument");
}
}
pub fn get_search_arg(varp: &typval_T, flagsp: Option<&mut i32>) -> i32 {
use crate::ported::search::{BACKWARD, FORWARD};
let mut dir = FORWARD;
if varp.v_type == VAR_UNKNOWN {
return FORWARD; }
let flags = match tv_get_string_buf_chk(varp) {
Some(f) => f,
None => return 0, };
let mut flagsp = flagsp;
for &c in flags.as_bytes() {
match c {
b'b' => dir = BACKWARD, b'w' => crate::ported::search::p_ws.with(|w| *w.borrow_mut() = true), b'W' => crate::ported::search::p_ws.with(|w| *w.borrow_mut() = false), _ => {
let mut mask = 0; if flagsp.is_some() {
mask = match c {
b'c' => SP_START,
b'e' => SP_END,
b'm' => SP_RETCOUNT,
b'n' => SP_NOMOVE,
b'p' => SP_SUBPAT,
b'r' => SP_REPEAT,
b's' => SP_SETPCMARK,
b'z' => SP_COLUMN,
_ => 0,
};
}
if mask == 0 {
crate::ported::message::semsg(&format!("E475: Invalid argument: {flags}"));
dir = 0;
} else if let Some(fp) = flagsp.as_deref_mut() {
*fp |= mask; }
}
}
if dir == 0 {
break; }
}
dir }
pub fn search_cmn(
argvars: &[typval_T],
match_pos: Option<&mut crate::ported::window::pos_T>,
flagsp: &mut i32,
) -> i32 {
use crate::ported::search::{searchit, SEARCH_COL, SEARCH_END, SEARCH_KEEP, SEARCH_START};
let save_p_ws = crate::ported::search::p_ws.with(|w| *w.borrow()); let mut retval = 0; let mut lnum_stop = 0; let mut options = SEARCH_KEEP; let mut use_skip = false;
let pat = tv_get_string(&argvars[0]);
let dir = get_search_arg(&argvars[1], Some(flagsp));
if dir == 0 {
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
let flags = *flagsp;
if flags & SP_START != 0 {
options |= SEARCH_START; }
if flags & SP_END != 0 {
options |= SEARCH_END; }
if flags & SP_COLUMN != 0 {
options |= SEARCH_COL; }
if argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN {
lnum_stop = tv_get_number_chk(&argvars[2], None) as crate::ported::window::linenr_T;
if lnum_stop < 0 {
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
if argvars[3].v_type != VAR_UNKNOWN {
let time_limit = tv_get_number_chk(&argvars[3], None);
if time_limit < 0 {
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
use_skip = crate::ported::eval::eval_expr_valid_arg(&argvars[4]); }
}
if (flags & (SP_REPEAT | SP_RETCOUNT)) != 0
|| ((flags & SP_NOMOVE != 0) && (flags & SP_SETPCMARK != 0))
{
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[1])
));
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
let curwin_rc = crate::ported::window::curwin.with(|c| c.borrow().clone());
let save_cursor = curwin_rc
.as_ref()
.map_or(crate::ported::window::pos_T::default(), |w| {
w.borrow().w_cursor
});
let mut pos = save_cursor;
let mut firstpos = crate::ported::window::pos_T::default(); let mut subpatnum;
loop {
subpatnum = searchit(&mut pos, dir, &pat, options, lnum_stop);
if firstpos.lnum != 0 && pos == firstpos {
subpatnum = FAIL;
}
if subpatnum == FAIL || !use_skip {
break; }
if firstpos.lnum == 0 {
firstpos = pos; }
if let Some(w) = &curwin_rc {
let save_pos = w.borrow().w_cursor;
w.borrow_mut().w_cursor = pos;
let do_skip = crate::ported::eval::eval_expr_to_bool(&argvars[4]);
w.borrow_mut().w_cursor = save_pos;
if !do_skip {
break; }
} else {
break;
}
options &= !SEARCH_START; }
if subpatnum != FAIL {
if flags & SP_SUBPAT != 0 {
retval = subpatnum; } else {
retval = pos.lnum; }
if let Some(w) = &curwin_rc {
w.borrow_mut().w_cursor = pos; }
if let Some(mp) = match_pos {
mp.lnum = pos.lnum; mp.col = pos.col + 1;
}
}
if flags & SP_NOMOVE != 0 {
if let Some(w) = &curwin_rc {
w.borrow_mut().w_cursor = save_cursor;
}
}
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws); retval
}
pub fn searchpair_cmn(
argvars: &[typval_T],
match_pos: Option<&mut crate::ported::window::pos_T>,
) -> i32 {
use crate::ported::search::do_searchpair;
let save_p_ws = crate::ported::search::p_ws.with(|w| *w.borrow()); let mut flags = 0; let mut retval = 0; let mut lnum_stop = 0; let mut time_limit = 0i64;
let spat = tv_get_string_chk(&argvars[0]);
let mpat = tv_get_string_buf_chk(&argvars[1]);
let epat = tv_get_string_buf_chk(&argvars[2]);
let (spat, mpat, epat) = match (spat, mpat, epat) {
(Some(s), Some(m), Some(e)) => (s, m, e),
_ => {
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
};
let dir = get_search_arg(&argvars[3], Some(&mut flags));
if dir == 0 {
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
if (flags & (SP_END | SP_SUBPAT)) != 0
|| ((flags & SP_NOMOVE != 0) && (flags & SP_SETPCMARK != 0))
{
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[3])
));
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
if flags & SP_REPEAT != 0 {
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = false);
}
let mut skip: Option<&typval_T> = None;
if !(argvars[3].v_type == VAR_UNKNOWN || argvars[4].v_type == VAR_UNKNOWN) {
skip = Some(&argvars[4]); if argvars[5].v_type != VAR_UNKNOWN {
lnum_stop = tv_get_number_chk(&argvars[5], None) as crate::ported::window::linenr_T;
if lnum_stop < 0 {
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[5])
));
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
if argvars[6].v_type != VAR_UNKNOWN {
time_limit = tv_get_number_chk(&argvars[6], None);
if time_limit < 0 {
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
tv_get_string(&argvars[6])
));
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws);
return retval; }
}
}
}
retval = do_searchpair(
&spat, &mpat, &epat, dir, skip, flags, match_pos, lnum_stop, time_limit,
);
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = save_p_ws); retval
}
pub fn libcall_common(
argvars: &[typval_T],
rettv: &mut typval_T,
out_type: crate::ported::eval::typval_defs_h::VarType,
) {
rettv.v_type = out_type;
if out_type != VAR_NUMBER {
rettv.vval = v_string(String::new());
}
let (libname, funcname) = match (&argvars[0], &argvars[1]) {
(a, b) if a.v_type == VAR_STRING && b.v_type == VAR_STRING => {
(tv_get_string(a), tv_get_string(b))
}
_ => return, };
let in_type = argvars[2].v_type;
let str_in = if in_type == VAR_STRING {
Some(tv_get_string(&argvars[2]))
} else {
None
};
let int_in = tv_get_number(&argvars[2]) as i32;
let mut int_out = 0i32;
let mut str_out: Option<String> = None;
let success = if out_type == VAR_STRING {
crate::ported::os::dl::os_libcall(
Some(&libname),
Some(&funcname),
str_in.as_deref(),
int_in,
Some(&mut str_out),
&mut int_out,
)
} else {
crate::ported::os::dl::os_libcall(
Some(&libname),
Some(&funcname),
str_in.as_deref(),
int_in,
None,
&mut int_out,
)
};
if !success {
crate::ported::message::semsg(&format!("E364: Library call failed for \"{funcname}\""));
return;
}
if out_type == VAR_NUMBER {
rettv.vval = v_number(int_out as varnumber_T); } else {
rettv.vval = v_string(str_out.unwrap_or_default());
}
}
pub fn create_environment(
job_env: Option<&crate::ported::eval::typval_defs_h::dict_T>,
clear_env: bool,
pty: bool,
set_nvim_addr: bool,
pty_term_name: &str,
) -> std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>> {
let env = crate::ported::eval::typval::tv_dict_alloc();
if !clear_env {
for (name, value) in std::env::vars() {
tv_dict_add_str(&mut env.borrow_mut(), &name, &value); }
if pty {
for var in PTY_IGNORED_ENV_VARS {
tv_dict_item_remove(&mut env.borrow_mut(), var);
}
}
}
if pty {
tv_dict_item_remove(&mut env.borrow_mut(), "TERM");
tv_dict_add_str(&mut env.borrow_mut(), "TERM", pty_term_name);
}
if set_nvim_addr {
let nvim_addr = get_vim_var_str(crate::ported::eval::vars::vv::VV_SEND_SERVER);
if !nvim_addr.is_empty() {
tv_dict_item_remove(&mut env.borrow_mut(), "NVIM");
tv_dict_add_str(&mut env.borrow_mut(), "NVIM", &nvim_addr);
}
}
if let Some(je) = job_env {
tv_dict_extend(&mut env.borrow_mut(), je, "force");
}
if pty {
for var in REQUIRED_ENV_VARS {
let present = env.borrow().dv_hashtab.contains_key(*var);
if !present {
if let Ok(val) = std::env::var(var) {
tv_dict_add_allocated_str(&mut env.borrow_mut(), var, val);
}
}
}
}
env }
const PTY_IGNORED_ENV_VARS: &[&str] = &["COLUMNS", "LINES", "TERMCAP", "COLORFGBG", "COLORTERM"];
const REQUIRED_ENV_VARS: &[&str] = &["HOME"];
pub fn msgpackparse_unpack_blob(
blob: &crate::ported::eval::typval_defs_h::blob_T,
ret_list: &mut crate::ported::eval::typval_defs_h::list_T,
) {
let data_all = &blob.bv_ga;
if data_all.is_empty() {
return;
}
let mut data: &[u8] = data_all;
let mut remaining = data_all.len();
while remaining != 0 {
let mut tv = typval_T::default();
let status = crate::ported::eval::decode::unpack_typval(&mut data, &mut remaining, &mut tv);
if status != crate::ported::mpack::MPACK_OK {
crate::ported::message::emsg("E5071: Failed to parse msgpack");
return;
}
tv_list_append_owned_tv(ret_list, tv);
}
}
pub fn msgpackparse_unpack_list(
list: &crate::ported::eval::typval_defs_h::list_T,
ret_list: &mut crate::ported::eval::typval_defs_h::list_T,
) {
use crate::ported::eval::encode::{encode_init_lrstate, encode_read_from_list};
if tv_list_len(list) == 0 {
return;
}
let first_is_string = list
.lv_items
.first()
.is_some_and(|it| it.li_tv.v_type == VAR_STRING);
if !first_is_string {
crate::ported::message::semsg("E475: Invalid argument: List item is not a string");
return;
}
const ARENA_BLOCK_SIZE: usize = 4096; let mut lrstate = encode_init_lrstate(list);
let mut bytes: Vec<u8> = Vec::new();
let mut chunk = vec![0u8; ARENA_BLOCK_SIZE];
loop {
let (status, read_bytes) = encode_read_from_list(&mut lrstate, list, &mut chunk);
bytes.extend_from_slice(&chunk[..read_bytes]);
if status == FAIL {
crate::ported::message::semsg("E475: Invalid argument: List item is not a string");
return;
}
if status == OK {
break; }
}
let mut data: &[u8] = &bytes;
let mut remaining = bytes.len();
while remaining != 0 {
let mut tv = typval_T::default();
let status = crate::ported::eval::decode::unpack_typval(&mut data, &mut remaining, &mut tv);
if status != crate::ported::mpack::MPACK_OK {
crate::ported::message::emsg("E5071: Failed to parse msgpack");
return;
}
tv_list_append_owned_tv(ret_list, tv);
}
}
pub fn block_def2str(bd: &block_def) -> String {
let mut ret = String::new();
for _ in 0..bd.startspaces.max(0) {
ret.push(' ');
}
let tl = bd.textlen.max(0) as usize;
if tl > 0 {
let end = (bd.textstart_off as usize + tl).min(bd.line.len());
let start = (bd.textstart_off as usize).min(end);
ret.push_str(&bd.line[start..end]);
}
for _ in 0..bd.endspaces.max(0) {
ret.push(' ');
}
ret
}
#[derive(Default)]
pub struct block_def {
pub startspaces: crate::ported::window::colnr_T,
pub endspaces: crate::ported::window::colnr_T,
pub textlen: crate::ported::window::colnr_T,
pub textcol: crate::ported::window::colnr_T,
pub textstart_off: crate::ported::window::colnr_T,
pub is_oneChar: bool,
pub start_vcol: crate::ported::window::colnr_T,
pub end_vcol: crate::ported::window::colnr_T,
pub start_char_vcols: crate::ported::window::colnr_T,
pub line: String,
}
pub fn add_regionpos_range(
rettv: &mut typval_T,
p1: crate::ported::window::pos_T,
p2: crate::ported::window::pos_T,
) {
let fnum = crate::ported::buffer::curbuf
.with(|c| c.borrow().clone())
.map_or(0, |b| b.borrow().handle) as varnumber_T;
let l1 = crate::ported::eval::typval::tv_list_alloc(2); let l2 = crate::ported::eval::typval::tv_list_alloc(4); let l3 = crate::ported::eval::typval::tv_list_alloc(4);
{
let mut b = l2.borrow_mut();
tv_list_append_number(&mut b, fnum);
tv_list_append_number(&mut b, p1.lnum as varnumber_T);
tv_list_append_number(&mut b, p1.col as varnumber_T);
tv_list_append_number(&mut b, p1.coladd as varnumber_T);
}
{
let mut b = l3.borrow_mut();
tv_list_append_number(&mut b, fnum);
tv_list_append_number(&mut b, p2.lnum as varnumber_T);
tv_list_append_number(&mut b, p2.col as varnumber_T);
tv_list_append_number(&mut b, p2.coladd as varnumber_T);
}
tv_list_append_list(&mut l1.borrow_mut(), l2);
tv_list_append_list(&mut l1.borrow_mut(), l3);
if let v_list(Some(rl)) = &rettv.vval {
tv_list_append_list(&mut rl.borrow_mut(), l1);
}
}
#[allow(clippy::too_many_arguments)]
pub fn getregionpos(
argvars: &[typval_T],
rettv: &mut typval_T,
p1: &mut crate::ported::window::pos_T,
p2: &mut crate::ported::window::pos_T,
inclusive: &mut bool,
region_type: &mut Option<MotionType>,
oap: &mut oparg_T,
) -> i32 {
tv_list_alloc_ret(rettv, 0);
if tv_check_for_list_arg(argvars, 0) == FAIL
|| tv_check_for_list_arg(argvars, 1) == FAIL
|| tv_check_for_opt_dict_arg(argvars, 2) == FAIL
{
return FAIL;
}
let mut fnum1 = -1;
let mut fnum2 = -1;
if crate::ported::eval::list2fpos(&argvars[0], p1, Some(&mut fnum1), None, false) != OK
|| crate::ported::eval::list2fpos(&argvars[1], p2, Some(&mut fnum2), None, false) != OK
|| fnum1 != fnum2
{
return FAIL; }
let sel_excl = tv_get_string(&crate::ported::option::get_option_value("selection"))
.as_bytes()
.first()
== Some(&b'e');
let mut is_select_exclusive = sel_excl;
let mut type_str = String::from("v"); if argvars[2].v_type == VAR_DICT {
if let v_dict(Some(d)) = &argvars[2].vval {
is_select_exclusive =
tv_dict_get_bool(&d.borrow(), "exclusive", sel_excl as varnumber_T) != 0;
if let Some(t) = tv_dict_get_string(&d.borrow(), "type") {
type_str = t;
}
}
}
let tb = type_str.as_bytes();
let mut block_width = 0i32;
if tb == b"v" {
*region_type = Some(MotionType::CharWise); } else if tb == b"V" {
*region_type = Some(MotionType::LineWise); } else if tb.first() == Some(&0x16) {
let rest = &type_str[1..];
if !rest.is_empty() {
match rest.parse::<i32>() {
Ok(w) if w > 0 => block_width = w,
_ => {
crate::ported::message::semsg(&format!(
"E475: Invalid value for argument type: {type_str}"
));
return FAIL;
}
}
}
*region_type = Some(MotionType::BlockWise(block_width)); } else {
crate::ported::message::semsg(&format!(
"E475: Invalid value for argument type: {type_str}"
));
return FAIL; }
let findbuf = if fnum1 != 0 {
crate::ported::buffer::buflist_findnr(fnum1)
} else {
crate::ported::buffer::curbuf.with(|c| c.borrow().clone())
};
let findbuf = match findbuf {
Some(b) if b.borrow().b_ml.ml_mfp => b,
_ => {
crate::ported::message::emsg("E681: Buffer is not loaded");
return FAIL; }
};
let line_count = findbuf.borrow().b_ml.ml_line_count;
if p1.lnum < 1 || p1.lnum > line_count {
crate::ported::message::semsg(&format!("E966: Invalid line number: {}", p1.lnum));
return FAIL;
}
let l1len = crate::ported::buffer::ml_get_buf_len(&mut findbuf.borrow_mut(), p1.lnum);
if p1.col == MAXCOL {
p1.col = l1len + 1;
} else if p1.col < 1 || p1.col > l1len + 1 {
crate::ported::message::semsg(&format!("E964: Invalid column number: {}", p1.col));
return FAIL;
}
if p2.lnum < 1 || p2.lnum > line_count {
crate::ported::message::semsg(&format!("E966: Invalid line number: {}", p2.lnum));
return FAIL;
}
let l2len = crate::ported::buffer::ml_get_buf_len(&mut findbuf.borrow_mut(), p2.lnum);
if p2.col == MAXCOL {
p2.col = l2len + 1;
} else if p2.col < 1 || p2.col > l2len + 1 {
crate::ported::message::semsg(&format!("E964: Invalid column number: {}", p2.col));
return FAIL;
}
crate::ported::buffer::curbuf.with(|c| *c.borrow_mut() = Some(findbuf.clone()));
p1.col -= 1;
p2.col -= 1;
if !lt(*p1, *p2) {
std::mem::swap(p1, p2);
}
match region_type {
Some(MotionType::CharWise) => {
if is_select_exclusive && *p1 != *p2 {
if p2.col > 0 {
p2.col -= 1;
*inclusive = true;
} else {
*inclusive = false;
}
}
let line = crate::ported::buffer::ml_get_buf(&mut findbuf.borrow_mut(), p2.lnum);
if *inclusive && line.as_bytes().get(p2.col as usize).is_none() {
*inclusive = false;
}
}
Some(MotionType::BlockWise(bw)) => {
let wp = crate::ported::window::curwin
.with(|c| c.borrow().clone())
.expect("curwin");
let l1 = crate::ported::buffer::ml_get_buf(&mut findbuf.borrow_mut(), p1.lnum);
let l2 = crate::ported::buffer::ml_get_buf(&mut findbuf.borrow_mut(), p2.lnum);
let sc1 = crate::ported::plines::getvcol(&wp, &l1, p1.col);
let sc2 = crate::ported::plines::getvcol(&wp, &l2, p2.col);
let ec1 = sc1;
let ec2 = sc2;
oap.motion_type = Some(MotionType::BlockWise(*bw));
oap.inclusive = true;
oap.op_type = 0; oap.start = *p1;
oap.end = *p2;
oap.start_vcol = sc1.min(sc2);
oap.end_vcol = if *bw > 0 {
oap.start_vcol + *bw - 1
} else {
ec1.max(ec2)
};
}
_ => {}
}
OK }
#[derive(Default)]
pub struct oparg_T {
pub motion_type: Option<MotionType>,
pub inclusive: bool,
pub op_type: i32,
pub start: crate::ported::window::pos_T,
pub end: crate::ported::window::pos_T,
pub start_vcol: crate::ported::window::colnr_T,
pub end_vcol: crate::ported::window::colnr_T,
}
fn lt(a: crate::ported::window::pos_T, b: crate::ported::window::pos_T) -> bool {
if a.lnum != b.lnum {
a.lnum < b.lnum
} else if a.col != b.col {
a.col < b.col
} else {
a.coladd < b.coladd
}
}
type DoCmdlineFn = fn(&str) -> String;
thread_local! {
static msg_silent: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static msg_col: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static emsg_noredir: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
static redir_off: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
static capture_ga: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
pub static DO_CMDLINE_HOOK: std::cell::RefCell<Option<DoCmdlineFn>> =
const { std::cell::RefCell::new(None) };
}
pub fn execute_common(argvars: &[typval_T], rettv: &mut typval_T, arg_off: usize) {
let do_cmdline_via_hook = |cmd: &str| -> String {
match DO_CMDLINE_HOOK.with(|h| *h.borrow()) {
Some(run) => run(cmd),
None => String::new(),
}
};
let save_msg_silent = msg_silent.with(|v| v.get()); let save_emsg_silent = emsg_silent.with(|v| v.get()); let save_emsg_noredir = emsg_noredir.with(|v| v.get()); let save_redir_off = redir_off.with(|v| v.get()); let save_capture_ga = capture_ga.with(|v| v.borrow().clone()); let save_msg_col = msg_col.with(|v| v.get()); let mut echo_output = false;
if argvars[arg_off + 1].v_type != VAR_UNKNOWN {
let s = match tv_get_string_buf_chk(&argvars[arg_off + 1]) {
Some(s) => s,
None => return,
};
if s.is_empty() {
echo_output = true;
}
if s.as_bytes().starts_with(b"silent") {
msg_silent.with(|v| v.set(v.get() + 1));
}
if s == "silent!" {
emsg_silent.with(|v| v.set(1));
emsg_noredir.with(|v| v.set(true));
}
} else {
msg_silent.with(|v| v.set(v.get() + 1));
}
capture_ga.with(|v| *v.borrow_mut() = Some(String::new()));
redir_off.with(|v| v.set(false)); if !echo_output {
msg_col.with(|v| v.set(0));
}
let output = if argvars[arg_off].v_type != VAR_LIST {
do_cmdline_via_hook(&tv_get_string(&argvars[arg_off]))
} else {
match &argvars[arg_off].vval {
v_list(Some(l)) => {
tv_list_ref(&mut l.borrow_mut());
let joined = l
.borrow()
.lv_items
.iter()
.map(|it| tv_get_string(&it.li_tv))
.collect::<Vec<_>>()
.join("\n");
let out = do_cmdline_via_hook(&joined);
l.borrow_mut().lv_refcount -= 1;
out
}
_ => String::new(),
}
};
capture_ga.with(|v| {
if let Some(buf) = v.borrow_mut().as_mut() {
buf.push_str(&output);
}
});
msg_silent.with(|v| v.set(save_msg_silent)); emsg_silent.with(|v| v.set(save_emsg_silent)); emsg_noredir.with(|v| v.set(save_emsg_noredir)); redir_off.with(|v| v.set(save_redir_off)); if echo_output {
msg_col.with(|v| v.set(0));
} else {
msg_col.with(|v| v.set(save_msg_col));
}
let captured = capture_ga
.with(|v| v.borrow_mut().take())
.unwrap_or_default();
rettv.v_type = VAR_STRING; rettv.vval = v_string(captured);
capture_ga.with(|v| *v.borrow_mut() = save_capture_ga);
}
pub fn f_execute(argvars: &[typval_T], rettv: &mut typval_T) {
execute_common(argvars, rettv, 0);
}
fn screenchar_adjust(grid: &mut Option<ScreenGrid>, row: &mut i32, col: &mut i32) {
*grid = ui_comp_get_grid_at_coord(*row, *col);
if let Some(g) = grid {
*row -= g.comp_row; *col -= g.comp_col; }
}
#[cfg(test)]
mod execute_screen_reference_tests {
use super::*;
use crate::ported::eval::typval_defs_h::listitem_T;
#[test]
fn execute_returns_hook_captured_text() {
fn hook(cmd: &str) -> String {
format!("ran:{cmd}")
}
let saved = DO_CMDLINE_HOOK.with(|h| *h.borrow());
DO_CMDLINE_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let args = vec![typval_T::from("echo x".to_string()), typval_T::default()];
let mut rettv = typval_T::default();
f_execute(&args, &mut rettv);
match &rettv.vval {
v_string(s) => assert_eq!(s, "ran:echo x"),
_ => panic!("expected VAR_STRING capture"),
}
assert!(matches!(rettv.v_type, VAR_STRING));
DO_CMDLINE_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn execute_list_joins_lines() {
fn hook(cmd: &str) -> String {
cmd.to_string()
}
let saved = DO_CMDLINE_HOOK.with(|h| *h.borrow());
DO_CMDLINE_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let mut list = list_T::default();
list.lv_items.push(listitem_T {
li_tv: typval_T::from("one".to_string()),
});
list.lv_items.push(listitem_T {
li_tv: typval_T::from("two".to_string()),
});
list.lv_len = 2;
let cmds = typval_T {
v_type: VAR_LIST,
vval: v_list(Some(std::rc::Rc::new(std::cell::RefCell::new(list)))),
v_lock: Default::default(),
};
let args = vec![cmds, typval_T::default()];
let mut rettv = typval_T::default();
f_execute(&args, &mut rettv);
match &rettv.vval {
v_string(s) => assert_eq!(s, "one\ntwo"),
_ => panic!("expected VAR_STRING capture"),
}
DO_CMDLINE_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn execute_without_hook_is_empty() {
let saved = DO_CMDLINE_HOOK.with(|h| *h.borrow());
DO_CMDLINE_HOOK.with(|h| *h.borrow_mut() = None);
let args = vec![typval_T::from("echo x".to_string()), typval_T::default()];
let mut rettv = typval_T::default();
f_execute(&args, &mut rettv);
match &rettv.vval {
v_string(s) => assert!(s.is_empty()),
_ => panic!("expected VAR_STRING capture"),
}
DO_CMDLINE_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn screenchar_adjust_no_grid_leaves_coords() {
let mut grid: Option<ScreenGrid> = None;
let mut row = 5;
let mut col = 7;
screenchar_adjust(&mut grid, &mut row, &mut col);
assert!(grid.is_none());
assert_eq!((row, col), (5, 7));
}
}
#[cfg(test)]
mod phaseg_reference_tests {
use super::*;
use crate::ported::eval::typval::EVAL_STRING_HOOK;
use crate::ported::eval::typval_defs_h::{blob_T, list_T, typval_T};
#[test]
fn f_eval_evaluates_number() {
fn hook(e: &str) -> Option<typval_T> {
e.trim().parse::<i64>().ok().map(typval_T::from)
}
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let args = vec![typval_T::from("42".to_string())];
let mut rettv = typval_T::default();
f_eval(&args, &mut rettv);
assert!(matches!(rettv.vval, v_number(42)));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn get_search_arg_parses_flags() {
use crate::ported::search::{BACKWARD, FORWARD};
crate::ported::search::p_ws.with(|w| *w.borrow_mut() = true);
let mut flags = 0;
let varp = typval_T::from("bWnc".to_string());
let dir = get_search_arg(&varp, Some(&mut flags));
assert_eq!(dir, BACKWARD);
assert!(!crate::ported::search::p_ws.with(|w| *w.borrow()));
assert_eq!(flags & SP_NOMOVE, SP_NOMOVE);
assert_eq!(flags & SP_START, SP_START);
let unk = typval_T::default();
let mut f2 = 0;
assert_eq!(get_search_arg(&unk, Some(&mut f2)), FORWARD);
assert_eq!(f2, 0);
}
#[test]
fn block_def2str_pads_and_copies() {
let bd = block_def {
startspaces: 2,
endspaces: 1,
textlen: 3,
textstart_off: 1,
line: "abcdef".to_string(),
..Default::default()
};
assert_eq!(block_def2str(&bd), " bcd ");
}
#[test]
fn msgpackparse_blob_roundtrip_fixint() {
let mut blob = blob_T::default();
blob.bv_ga = vec![0x01, 0x02];
let mut out = list_T::default();
msgpackparse_unpack_blob(&blob, &mut out);
assert_eq!(out.lv_items.len(), 2);
assert!(matches!(out.lv_items[0].li_tv.vval, v_number(1)));
assert!(matches!(out.lv_items[1].li_tv.vval, v_number(2)));
}
#[test]
fn create_environment_includes_and_overrides() {
std::env::set_var("VIMLRS_TEST_ENV", "inherited");
let mut je = crate::ported::eval::typval_defs_h::dict_T::default();
crate::ported::eval::typval::tv_dict_add_str(&mut je, "VIMLRS_TEST_ENV", "overridden");
let env = create_environment(Some(&je), false, false, false, "");
let got = env.borrow().dv_hashtab.get("VIMLRS_TEST_ENV").cloned();
match got {
Some(tv) => assert!(matches!(&tv.vval, v_string(s) if s == "overridden")),
None => panic!("env var missing"),
}
std::env::remove_var("VIMLRS_TEST_ENV");
}
}
#[cfg(test)]
mod helper_tests {
use super::may_add_state_char;
#[test]
fn find_internal_func_lookup() {
use super::find_internal_func;
let add = find_internal_func("add").unwrap();
assert_eq!((add.min_argc, add.max_argc, add.base_arg), (2, 2, 1));
assert_eq!(find_internal_func("argc").unwrap().base_arg, 0); assert!(find_internal_func("not_a_builtin_xyz").is_none());
}
#[test]
fn check_and_call_internal_method() {
use super::{call_internal_method, check_internal_func};
use crate::ported::eval::typval::CALL_FUNC_HOOK;
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
use crate::ported::eval::userfunc::fcerr::*;
assert_eq!(check_internal_func("add", 2), 1);
assert_eq!(check_internal_func("add", 1), -1); fn hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(args.len() as i64))
}
let saved = CALL_FUNC_HOOK.with(|h| *h.borrow());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let mut rv = typval_T::from(0);
assert_eq!(
call_internal_method("add", &[typval_T::from(9)], &typval_T::from(1), &mut rv),
FCERR_NONE
);
assert!(matches!(rv.vval, v_number(2)));
assert_eq!(
call_internal_method("no_such_xyz", &[], &typval_T::from(0), &mut rv),
FCERR_UNKNOWN
);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn call_internal_func_arity_and_dispatch() {
use super::call_internal_func;
use crate::ported::eval::typval::CALL_FUNC_HOOK;
use crate::ported::eval::typval_defs_h::typval_T;
use crate::ported::eval::userfunc::fcerr::*;
fn hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(args.len() as i64))
}
let mut rv = typval_T::from(0);
assert_eq!(
call_internal_func("no_such_builtin_xyz", &[], &mut rv),
FCERR_UNKNOWN
);
assert_eq!(
call_internal_func("add", &[typval_T::from(1)], &mut rv),
FCERR_TOOFEW
);
assert_eq!(
call_internal_func(
"add",
&[typval_T::from(1), typval_T::from(2), typval_T::from(3)],
&mut rv
),
FCERR_TOOMANY
);
let saved = CALL_FUNC_HOOK.with(|h| *h.borrow());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = None);
assert_eq!(
call_internal_func("add", &[typval_T::from(1), typval_T::from(2)], &mut rv),
FCERR_UNKNOWN
);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook));
assert_eq!(
call_internal_func("add", &[typval_T::from(1), typval_T::from(2)], &mut rv),
FCERR_NONE
);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn return_register_char_or_empty() {
use super::return_register;
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_string, VarType::VAR_STRING,
};
let mut tv = typval_T::from(0);
return_register(b'a', &mut tv);
assert!(matches!(&tv.vval, v_string(s) if s == "a"));
assert_eq!(tv.v_type, VAR_STRING);
return_register(0, &mut tv);
assert!(matches!(&tv.vval, v_string(s) if s.is_empty()));
}
#[test]
fn may_add_state_char_filters() {
let mut all = String::new();
for c in ['m', 'o', 'x'] {
may_add_state_char(&mut all, None, c);
}
assert_eq!(all, "mox");
let mut some = String::new();
for c in ['m', 'o', 'x'] {
may_add_state_char(&mut some, Some("mx"), c);
}
assert_eq!(some, "mx");
}
#[test]
fn tv_get_buf_number_name_and_specials() {
use super::{get_buf_arg, tv_get_buf, tv_get_buf_from_arg};
use crate::ported::buffer::{
buflist_new, curbuf, firstbuf, lastbuf, top_file_num, BLN_LISTED,
};
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_float, VarLockStatus, VarType::VAR_FLOAT,
};
use crate::ported::message::{capture_errors_begin, capture_errors_take};
use std::rc::Rc;
firstbuf.with(|f| *f.borrow_mut() = None);
lastbuf.with(|l| *l.borrow_mut() = None);
curbuf.with(|c| *c.borrow_mut() = None);
top_file_num.with(|t| t.set(1));
let a = buflist_new(Some("/tmp/a".into()), None, 0, BLN_LISTED).unwrap();
let b = buflist_new(Some("/tmp/b".into()), None, 0, BLN_LISTED).unwrap();
curbuf.with(|c| *c.borrow_mut() = Some(a.clone()));
assert!(Rc::ptr_eq(
&tv_get_buf(&typval_T::from(1i64), false).unwrap(),
&a
));
assert!(Rc::ptr_eq(
&tv_get_buf(&typval_T::from(String::new()), false).unwrap(),
&a
));
assert!(Rc::ptr_eq(
&tv_get_buf(&typval_T::from(String::from("$")), false).unwrap(),
&b
));
assert!(tv_get_buf(&typval_T::from(999i64), false).is_none());
let flt = typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(1.5),
};
capture_errors_begin();
assert!(tv_get_buf_from_arg(&flt).is_none());
assert!(capture_errors_take().iter().any(|e| e.starts_with("E805")));
assert!(Rc::ptr_eq(
&tv_get_buf_from_arg(&typval_T::from(1i64)).unwrap(),
&a
));
capture_errors_begin();
assert!(get_buf_arg(&typval_T::from(999i64)).is_none());
assert!(capture_errors_take()
.iter()
.any(|e| e.starts_with("E158: Invalid buffer name: 999")));
}
}