#![allow(non_snake_case, dead_code, clippy::all)]
use std::cell::RefCell;
use std::rc::Rc;
use crate::ported::buffer::{
bt_nofilename, buf_T, buf_get_changedtick, buflist_findlnum, buflist_findname_exp,
buflist_findnr, colnr_T, curbuf, linenr_T, ml_append, ml_get, ml_get_buf, ml_get_buf_len,
ml_replace,
};
use crate::ported::eval::funcs::{tv_get_buf, tv_get_buf_from_arg};
use crate::ported::eval::typval::{
tv_dict_add_dict, tv_dict_add_list, tv_dict_add_nr, tv_dict_add_str, tv_get_lnum_buf,
tv_list_alloc, tv_list_alloc_ret, tv_list_append_string, tv_list_len,
};
use crate::ported::eval::typval_defs_h::{dict_T, list_T, typval_T, typval_vval_union, VarType};
use crate::ported::eval::typval_tostring;
use crate::ported::eval_h::OK;
use crate::ported::message::did_emsg;
fn path_with_url(_fname: &str) -> bool {
false
}
fn bufIsChanged(buf: &buf_T) -> bool {
buf.b_changed != 0
}
fn buf_has_signs(_buf: &buf_T) -> bool {
false
}
fn u_savesub(_lnum: linenr_T) -> i32 {
OK
}
fn u_save(_top: linenr_T, _bot: linenr_T) -> i32 {
OK
}
fn inserted_bytes(_lnum: linenr_T, _start_col: colnr_T, _old_col: i32, _new_col: i32) {}
fn appended_lines_mark(_lnum: linenr_T, _count: i32) {}
#[derive(Default)]
struct cob_T {
cob_curbuf_save: Option<Rc<RefCell<buf_T>>>,
}
fn change_other_buffer_prepare(cob: &mut cob_T, buf: &Rc<RefCell<buf_T>>) {
cob.cob_curbuf_save = curbuf.with(|c| c.borrow_mut().replace(buf.clone()));
}
fn change_other_buffer_restore(cob: &mut cob_T) {
curbuf.with(|c| *c.borrow_mut() = cob.cob_curbuf_save.take());
}
pub fn find_buffer(avar: &typval_T) -> Option<Rc<RefCell<buf_T>>> {
let mut buf: Option<Rc<RefCell<buf_T>>> = None;
if avar.v_type == VarType::VAR_NUMBER {
if let typval_vval_union::v_number(n) = &avar.vval {
buf = buflist_findnr(*n as i32);
}
} else if avar.v_type == VarType::VAR_STRING {
if let typval_vval_union::v_string(s) = &avar.vval {
if !s.is_empty() {
buf = buflist_findname_exp(s);
if buf.is_none() {
let mut bp = crate::ported::buffer::firstbuf.with(|f| f.borrow().clone());
while let Some(b) = bp {
{
let bb = b.borrow();
if let Some(fname) = bb.b_fname.as_deref() {
if (path_with_url(fname) || bt_nofilename(&bb)) && fname == s {
drop(bb);
buf = Some(b);
break;
}
}
}
let next = b.borrow().b_next.clone();
bp = next;
}
}
}
}
}
buf
}
pub fn get_buffer_lines(
buf: Option<&Rc<RefCell<buf_T>>>,
mut start: i32,
end: i32,
retlist: bool,
rettv: &mut typval_T,
) {
rettv.v_type = if retlist {
VarType::VAR_LIST
} else {
VarType::VAR_STRING
};
rettv.vval = typval_vval_union::v_string(String::new());
let loaded = buf.map_or(false, |b| b.borrow().b_ml.ml_mfp);
if buf.is_none() || !loaded || start < 0 || end < start {
if retlist {
tv_list_alloc_ret(rettv, 0);
}
return;
}
let buf = buf.unwrap();
let line_count = buf.borrow().b_ml.ml_line_count;
if retlist {
if start < 1 {
start = 1;
}
let end = end.min(line_count);
let l = tv_list_alloc_ret(rettv, (end - start + 1) as isize);
let mut start = start;
while start <= end {
let mut b = buf.borrow_mut();
let line = ml_get_buf(&mut b, start);
drop(b);
tv_list_append_string(&mut l.borrow_mut(), &line);
start += 1;
}
} else {
rettv.v_type = VarType::VAR_STRING;
if start >= 1 && start <= line_count {
let mut b = buf.borrow_mut();
let _ = ml_get_buf_len(&mut b, start);
let line = ml_get_buf(&mut b, start);
drop(b);
rettv.vval = typval_vval_union::v_string(line);
} else {
rettv.vval = typval_vval_union::v_string(String::new());
}
}
}
pub fn set_buffer_lines(
buf: &Rc<RefCell<buf_T>>,
lnum_arg: linenr_T,
append: bool,
lines: &typval_T,
rettv: &mut typval_T,
) {
let mut lnum = lnum_arg + if append { 1 } else { 0 };
let mut added: i32 = 0;
let is_curbuf = curbuf.with(|c| c.borrow().as_ref().map_or(false, |cb| Rc::ptr_eq(cb, buf)));
if (!is_curbuf && !buf.borrow().b_ml.ml_mfp) || lnum < 1 {
rettv.vval = typval_vval_union::v_number(1);
return;
}
let mut cob = cob_T::default();
if !is_curbuf {
change_other_buffer_prepare(&mut cob, buf);
}
let line_count = || {
curbuf.with(|c| {
c.borrow()
.as_ref()
.map_or(0, |b| b.borrow().b_ml.ml_line_count)
})
};
let append_lnum = if append {
lnum - 1
} else {
line_count()
};
let l: Option<Rc<RefCell<list_T>>> = if lines.v_type == VarType::VAR_LIST {
if let typval_vval_union::v_list(x) = &lines.vval {
x.clone()
} else {
None
}
} else {
None
};
let mut li: usize = 0;
let mut line: Option<String>;
'cleanup: {
if lines.v_type == VarType::VAR_LIST {
match &l {
None => break 'cleanup,
Some(lst) if tv_list_len(&lst.borrow()) == 0 => break 'cleanup,
_ => {}
}
line = None;
} else {
line = Some(typval_tostring(Some(lines), false));
}
loop {
if lines.v_type == VarType::VAR_LIST {
let next = {
let lst = l.as_ref().unwrap().borrow();
if li >= lst.lv_items.len() {
None
} else {
Some(typval_tostring(Some(&lst.lv_items[li].li_tv), false))
}
};
match next {
None => break,
Some(s) => {
line = Some(s);
li += 1;
}
}
}
rettv.vval = typval_vval_union::v_number(1);
let count = line_count();
if lnum > count + 1 {
break;
}
let ln = line.as_deref().unwrap();
if !append && lnum <= count {
let old_len = ml_get(lnum).len() as i32;
if u_savesub(lnum) == OK && ml_replace(lnum, ln, true) == OK {
inserted_bytes(lnum, 0, old_len, ln.len() as i32);
rettv.vval = typval_vval_union::v_number(0);
}
} else if added > 0 || u_save(lnum - 1, lnum) == OK {
added += 1;
if ml_append(lnum - 1, ln, 0, false) == OK {
rettv.vval = typval_vval_union::v_number(0);
}
}
if l.is_none() {
break;
}
lnum += 1;
}
}
if added > 0 {
appended_lines_mark(append_lnum, added);
}
if !is_curbuf {
change_other_buffer_restore(&mut cob);
}
}
pub fn buf_set_append_line(argvars: &[typval_T], rettv: &mut typval_T, append: bool) {
let did_emsg_before = did_emsg.with(|d| d.get());
let buf = tv_get_buf(&argvars[0], false);
match buf {
None => {
rettv.vval = typval_vval_union::v_number(1);
}
Some(buf) => {
let lnum = tv_get_lnum_buf(&argvars[1], None) as linenr_T;
if did_emsg.with(|d| d.get()) == did_emsg_before {
set_buffer_lines(&buf, lnum, append, &argvars[2], rettv);
}
}
}
}
pub fn getbufline(argvars: &[typval_T], rettv: &mut typval_T, retlist: bool) {
let mut lnum: linenr_T = 1;
let mut end: linenr_T = 1;
let did_emsg_before = did_emsg.with(|d| d.get());
let buf = tv_get_buf_from_arg(&argvars[0]);
if buf.is_some() {
lnum = tv_get_lnum_buf(&argvars[1], None) as linenr_T;
if did_emsg.with(|d| d.get()) > did_emsg_before {
return;
}
end = if argvars.get(2).map_or(VarType::VAR_UNKNOWN, |a| a.v_type) == VarType::VAR_UNKNOWN {
lnum
} else {
tv_get_lnum_buf(&argvars[2], None) as linenr_T
};
}
get_buffer_lines(buf.as_ref(), lnum, end, retlist, rettv);
}
pub fn get_buffer_info(buf: &Rc<RefCell<buf_T>>) -> Rc<RefCell<dict_T>> {
let dict = crate::ported::eval::typval::tv_dict_alloc();
let is_curbuf = curbuf.with(|c| c.borrow().as_ref().map_or(false, |cb| Rc::ptr_eq(cb, buf)));
let b = buf.borrow();
{
let mut d = dict.borrow_mut();
tv_dict_add_nr(&mut d, "bufnr", b.handle as i64);
tv_dict_add_str(&mut d, "name", b.b_ffname.as_deref().unwrap_or(""));
let lnum = if is_curbuf {
buflist_findlnum(&b)
} else {
buflist_findlnum(&b)
};
tv_dict_add_nr(&mut d, "lnum", lnum as i64);
tv_dict_add_nr(&mut d, "linecount", b.b_ml.ml_line_count as i64);
tv_dict_add_nr(&mut d, "loaded", b.b_ml.ml_mfp as i64);
tv_dict_add_nr(&mut d, "listed", b.b_p_bl as i64);
tv_dict_add_nr(&mut d, "changed", bufIsChanged(&b) as i64);
tv_dict_add_nr(&mut d, "changedtick", buf_get_changedtick(&b));
tv_dict_add_nr(
&mut d,
"hidden",
(b.b_ml.ml_mfp && b.b_nwindows == 0) as i64,
);
tv_dict_add_nr(&mut d, "command", 0);
if let Some(vars) = b.b_vars.clone() {
tv_dict_add_dict(&mut d, "variables", vars);
}
}
let windows = tv_list_alloc(0);
tv_dict_add_list(&mut dict.borrow_mut(), "windows", windows);
let _ = buf_has_signs(&b);
tv_dict_add_nr(&mut dict.borrow_mut(), "lastused", b.b_last_used);
dict
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::buffer::{buflist_new, firstbuf, lastbuf, top_file_num, BLN_LISTED};
fn reset() {
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));
}
fn load(buf: &Rc<RefCell<buf_T>>, lines: &[&str]) {
let mut b = buf.borrow_mut();
b.b_ml.ml_mfp = true;
b.b_ml.ml_lines = lines.iter().map(|s| s.to_string()).collect();
b.b_ml.ml_line_count = lines.len() as i32;
}
#[test]
fn find_buffer_by_number_and_name() {
reset();
let a = buflist_new(Some("/tmp/a".into()), None, 0, BLN_LISTED).unwrap();
let tv = typval_T::from(1i64);
assert!(Rc::ptr_eq(&find_buffer(&tv).unwrap(), &a));
let tv = typval_T::from(String::from("/tmp/a"));
assert!(Rc::ptr_eq(&find_buffer(&tv).unwrap(), &a));
let tv = typval_T::from(42i64);
assert!(find_buffer(&tv).is_none());
}
#[test]
fn get_buffer_lines_list_and_single() {
reset();
let buf = buflist_new(Some("/tmp/b".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["alpha", "beta", "gamma"]);
let as_str = |tv: &typval_T| match &tv.vval {
typval_vval_union::v_string(s) => s.clone(),
_ => panic!("expected VAR_STRING"),
};
let mut rettv = typval_T::default();
get_buffer_lines(Some(&buf), 1, 3, true, &mut rettv);
assert_eq!(rettv.v_type, VarType::VAR_LIST);
if let typval_vval_union::v_list(Some(l)) = &rettv.vval {
let l = l.borrow();
assert_eq!(l.lv_len, 3);
assert_eq!(as_str(&l.lv_items[0].li_tv), "alpha");
assert_eq!(as_str(&l.lv_items[2].li_tv), "gamma");
} else {
panic!("expected VAR_LIST");
}
let mut rettv = typval_T::default();
get_buffer_lines(Some(&buf), 2, 2, false, &mut rettv);
assert_eq!(rettv.v_type, VarType::VAR_STRING);
assert_eq!(as_str(&rettv), "beta");
let mut rettv = typval_T::default();
get_buffer_lines(Some(&buf), 9, 9, false, &mut rettv);
assert_eq!(as_str(&rettv), "");
}
#[test]
fn get_buffer_info_fields() {
reset();
let buf = buflist_new(Some("/tmp/c".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["one", "two"]);
let d = get_buffer_info(&buf);
let d = d.borrow();
let nr = |k: &str| match d.dv_hashtab.get(k).map(|tv| &tv.vval) {
Some(typval_vval_union::v_number(n)) => *n,
_ => panic!("missing/!nr {k}"),
};
assert_eq!(nr("bufnr"), 1);
assert_eq!(nr("linecount"), 2);
assert_eq!(nr("loaded"), 1);
assert_eq!(nr("listed"), 1);
assert_eq!(nr("changed"), 0);
assert_eq!(nr("lnum"), 1);
match d.dv_hashtab.get("name").map(|tv| &tv.vval) {
Some(typval_vval_union::v_string(s)) => assert_eq!(s, "/tmp/c"),
_ => panic!("missing name"),
}
assert!(matches!(
d.dv_hashtab.get("variables").map(|tv| &tv.vval),
Some(typval_vval_union::v_dict(Some(_)))
));
}
use crate::ported::eval::typval_defs_h::listitem_T;
fn lines_of(buf: &Rc<RefCell<buf_T>>) -> Vec<String> {
buf.borrow().b_ml.ml_lines.clone()
}
fn nr(rettv: &typval_T) -> i64 {
match &rettv.vval {
typval_vval_union::v_number(n) => *n,
_ => panic!("expected VAR_NUMBER"),
}
}
fn list_tv(items: &[&str]) -> typval_T {
let l = Rc::new(RefCell::new(list_T::default()));
{
let mut lb = l.borrow_mut();
for it in items {
lb.lv_items.push(listitem_T {
li_tv: typval_T::from(it.to_string()),
});
}
lb.lv_len = items.len() as i32;
}
typval_T {
v_type: VarType::VAR_LIST,
vval: typval_vval_union::v_list(Some(l)),
..Default::default()
}
}
#[test]
fn buf_set_append_line_appends_and_sets() {
reset();
let buf = buflist_new(Some("/tmp/ap".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["a", "b", "c"]);
let mut rettv = typval_T::from(0i64);
let args = [
typval_T::from(buf.borrow().handle as i64),
typval_T::from(2i64),
typval_T::from(String::from("X")),
];
buf_set_append_line(&args, &mut rettv, true);
assert_eq!(nr(&rettv), 0); assert_eq!(lines_of(&buf), vec!["a", "b", "X", "c"]);
assert!(curbuf.with(|c| c.borrow().is_none()));
let mut rettv = typval_T::from(0i64);
let args = [
typval_T::from(buf.borrow().handle as i64),
typval_T::from(1i64),
typval_T::from(String::from("ONE")),
];
buf_set_append_line(&args, &mut rettv, false);
assert_eq!(nr(&rettv), 0);
assert_eq!(lines_of(&buf), vec!["ONE", "b", "X", "c"]);
}
#[test]
fn buf_set_append_line_list_and_missing_buffer() {
reset();
let buf = buflist_new(Some("/tmp/lst".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["a", "b"]);
let mut rettv = typval_T::from(0i64);
let args = [
typval_T::from(buf.borrow().handle as i64),
typval_T::from(1i64),
list_tv(&["x", "y"]),
];
buf_set_append_line(&args, &mut rettv, true);
assert_eq!(nr(&rettv), 0);
assert_eq!(lines_of(&buf), vec!["a", "x", "y", "b"]);
let mut rettv = typval_T::from(0i64);
let args = [
typval_T::from(999i64),
typval_T::from(1i64),
typval_T::from(String::from("z")),
];
buf_set_append_line(&args, &mut rettv, true);
assert_eq!(nr(&rettv), 1);
}
#[test]
fn getbufline_list_and_oneline() {
reset();
let buf = buflist_new(Some("/tmp/gl".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["one", "two", "three"]);
let h = buf.borrow().handle as i64;
let as_str = |tv: &typval_T| match &tv.vval {
typval_vval_union::v_string(s) => s.clone(),
_ => panic!("expected VAR_STRING"),
};
let mut rettv = typval_T::default();
let args = [
typval_T::from(h),
typval_T::from(1i64),
typval_T::from(3i64),
];
getbufline(&args, &mut rettv, true);
assert_eq!(rettv.v_type, VarType::VAR_LIST);
if let typval_vval_union::v_list(Some(l)) = &rettv.vval {
let l = l.borrow();
assert_eq!(l.lv_len, 3);
assert_eq!(as_str(&l.lv_items[0].li_tv), "one");
assert_eq!(as_str(&l.lv_items[2].li_tv), "three");
} else {
panic!("expected VAR_LIST");
}
let mut rettv = typval_T::default();
let args = [typval_T::from(h), typval_T::from(2i64)];
getbufline(&args, &mut rettv, false);
assert_eq!(rettv.v_type, VarType::VAR_STRING);
assert_eq!(as_str(&rettv), "two");
let mut rettv = typval_T::default();
let args = [
typval_T::from(4242i64),
typval_T::from(1i64),
typval_T::from(1i64),
];
getbufline(&args, &mut rettv, true);
assert_eq!(rettv.v_type, VarType::VAR_LIST);
if let typval_vval_union::v_list(Some(l)) = &rettv.vval {
assert_eq!(l.borrow().lv_len, 0);
} else {
panic!("expected empty VAR_LIST");
}
}
}