#![allow(non_snake_case, non_upper_case_globals, dead_code, clippy::all)]
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use crate::ported::eval::typval::tv_dict_alloc;
use crate::ported::eval::typval_defs_h::{dict_T, varnumber_T};
use crate::ported::eval_h::{FAIL, OK};
pub type linenr_T = i32;
pub type colnr_T = i32;
pub type handle_T = i32;
pub const BLN_CURBUF: i32 = 1;
pub const BLN_LISTED: i32 = 2;
pub const BLN_DUMMY: i32 = 4;
pub const BLN_NEW: i32 = 8;
pub const BLN_NOOPT: i32 = 16;
pub const BLN_NOCURWIN: i32 = 128;
pub const BF_CHECK_RO: i32 = 0x02;
pub const BF_NEVERLOADED: i32 = 0x04;
pub const BF_DUMMY: i32 = 0x80;
#[derive(Debug, Default)]
pub struct memline_T {
pub ml_line_count: linenr_T,
pub ml_mfp: bool,
pub ml_line_textlen: colnr_T,
pub ml_lines: Vec<String>,
}
#[derive(Debug, Default)]
pub struct buf_T {
pub handle: handle_T,
pub b_ml: memline_T,
pub b_next: Option<Rc<RefCell<buf_T>>>,
pub b_prev: Option<Weak<RefCell<buf_T>>>,
pub b_nwindows: i32,
pub b_flags: i32,
pub b_ffname: Option<String>,
pub b_sfname: Option<String>,
pub b_fname: Option<String>,
pub b_changed: i32,
pub changedtick: varnumber_T,
pub terminal: bool,
pub b_last_used: i64,
pub b_p_bl: i32,
pub b_p_ma: i32,
pub b_p_ro: i32,
pub b_p_bt: String,
pub b_modified_was_set: bool,
pub b_help: bool,
pub b_vars: Option<Rc<RefCell<dict_T>>>,
}
thread_local! {
pub static firstbuf: RefCell<Option<Rc<RefCell<buf_T>>>> = const { RefCell::new(None) };
pub static lastbuf: RefCell<Option<Rc<RefCell<buf_T>>>> = const { RefCell::new(None) };
pub static curbuf: RefCell<Option<Rc<RefCell<buf_T>>>> = const { RefCell::new(None) };
pub static top_file_num: Cell<i32> = const { Cell::new(1) };
}
fn handle_get_buffer(nr: handle_T) -> Option<Rc<RefCell<buf_T>>> {
let mut cur = firstbuf.with(|f| f.borrow().clone());
while let Some(buf) = cur {
if buf.borrow().handle == nr {
return Some(buf);
}
let next = buf.borrow().b_next.clone();
cur = next;
}
None
}
fn path_fnamecmp(a: &str, b: &str) -> i32 {
if a == b {
0
} else {
1
}
}
fn FullName_save(fname: &str, _force: bool) -> Option<String> {
Some(fname.to_string())
}
pub fn buf_get_changedtick(buf: &buf_T) -> varnumber_T {
buf.changedtick
}
pub fn ml_get_buf(buf: &mut buf_T, lnum: linenr_T) -> String {
if !buf.b_ml.ml_mfp {
buf.b_ml.ml_line_textlen = 1;
return String::new();
}
if lnum > buf.b_ml.ml_line_count {
buf.b_ml.ml_line_textlen = 4;
return "???".to_string();
}
let lnum = lnum.max(1);
let line = buf.b_ml.ml_lines[(lnum - 1) as usize].clone();
buf.b_ml.ml_line_textlen = line.len() as colnr_T + 1;
line
}
pub fn ml_get(lnum: linenr_T) -> String {
let cur = curbuf.with(|c| c.borrow().clone()).expect("curbuf is NULL");
let mut b = cur.borrow_mut();
ml_get_buf(&mut b, lnum)
}
pub fn ml_get_buf_len(buf: &mut buf_T, lnum: linenr_T) -> colnr_T {
let line = ml_get_buf(buf, lnum);
if line.is_empty() {
return 0;
}
buf.b_ml.ml_line_textlen - 1
}
pub fn ml_append_buf(
buf: &mut buf_T,
lnum: linenr_T,
line: &str,
_len: colnr_T,
_newfile: bool,
) -> i32 {
if !buf.b_ml.ml_mfp {
return FAIL;
}
if lnum < 0 || lnum > buf.b_ml.ml_line_count {
return FAIL;
}
buf.b_ml.ml_lines.insert(lnum as usize, line.to_string());
buf.b_ml.ml_line_count += 1;
OK
}
pub fn ml_append(lnum: linenr_T, line: &str, len: colnr_T, newfile: bool) -> i32 {
let cur = curbuf.with(|c| c.borrow().clone()).expect("curbuf is NULL");
let mut b = cur.borrow_mut();
ml_append_buf(&mut b, lnum, line, len, newfile)
}
pub fn ml_replace_buf(
buf: &mut buf_T,
lnum: linenr_T,
line: &str,
_copy: bool,
_noalloc: bool,
) -> i32 {
if lnum < 1 || lnum > buf.b_ml.ml_line_count {
return FAIL;
}
buf.b_ml.ml_lines[(lnum - 1) as usize] = line.to_string();
OK
}
pub fn ml_replace(lnum: linenr_T, line: &str, copy: bool) -> i32 {
let cur = curbuf.with(|c| c.borrow().clone()).expect("curbuf is NULL");
let mut b = cur.borrow_mut();
ml_replace_buf(&mut b, lnum, line, copy, false)
}
pub fn ml_delete_flags(lnum: linenr_T, _flags: i32) -> i32 {
let cur = curbuf.with(|c| c.borrow().clone()).expect("curbuf is NULL");
let mut buf = cur.borrow_mut();
if lnum < 1 || lnum > buf.b_ml.ml_line_count {
return FAIL;
}
buf.b_ml.ml_lines.remove((lnum - 1) as usize);
buf.b_ml.ml_line_count -= 1;
OK
}
pub fn ml_delete(lnum: linenr_T) -> i32 {
ml_delete_flags(lnum, 0)
}
fn otherfile_buf(buf: &buf_T, ffname: Option<&str>) -> bool {
let ffname = match ffname {
Some(s) if !s.is_empty() => s,
_ => return true,
};
let b_ffname = match buf.b_ffname.as_deref() {
Some(s) => s,
None => return true,
};
if path_fnamecmp(ffname, b_ffname) == 0 {
return false;
}
true
}
fn buflist_findname_file_id(ffname: &str) -> Option<Rc<RefCell<buf_T>>> {
let mut cur = lastbuf.with(|l| l.borrow().clone());
while let Some(buf) = cur {
{
let b = buf.borrow();
if (b.b_flags & BF_DUMMY) == 0 && !otherfile_buf(&b, Some(ffname)) {
drop(b);
return Some(buf);
}
}
let prev = buf.borrow().b_prev.as_ref().and_then(|w| w.upgrade());
cur = prev;
}
None
}
pub fn buflist_findname(ffname: &str) -> Option<Rc<RefCell<buf_T>>> {
buflist_findname_file_id(ffname)
}
pub fn buflist_findname_exp(fname: &str) -> Option<Rc<RefCell<buf_T>>> {
match FullName_save(fname, true) {
Some(ffname) => buflist_findname(&ffname),
None => None,
}
}
pub fn buflist_findnr(mut nr: i32) -> Option<Rc<RefCell<buf_T>>> {
if nr == 0 {
nr = 0;
}
handle_get_buffer(nr as handle_T)
}
pub fn buflist_findpat(pattern: &str, _unlisted: bool, _diffmode: bool, _curtab_only: bool) -> i32 {
let mut r#match: i32 = -1;
if pattern.len() == 1 && (pattern == "%" || pattern == "#") {
if pattern == "%" {
r#match = curbuf.with(|c| c.borrow().as_ref().map_or(-1, |b| b.borrow().handle));
}
} else {
let mut cur = lastbuf.with(|l| l.borrow().clone());
while let Some(buf) = cur {
{
let b = buf.borrow();
let hit = [&b.b_ffname, &b.b_sfname, &b.b_fname]
.iter()
.filter_map(|n| n.as_deref())
.any(|n| n.contains(pattern));
if b.b_p_bl != 0 && hit {
if r#match >= 0 {
return -2;
}
r#match = b.handle;
}
}
let prev = buf.borrow().b_prev.as_ref().and_then(|w| w.upgrade());
cur = prev;
}
}
r#match
}
pub fn buflist_nr2name(n: i32, fullname: bool, _helptail: bool) -> Option<String> {
let buf = buflist_findnr(n)?;
let b = buf.borrow();
if fullname {
b.b_ffname.clone()
} else {
b.b_fname.clone()
}
}
fn buflist_findfmark(_buf: &buf_T) -> linenr_T {
1
}
pub fn buflist_findlnum(buf: &buf_T) -> linenr_T {
buflist_findfmark(buf)
}
pub fn buflist_name_nr(fnum: i32) -> Option<(String, linenr_T)> {
let buf = buflist_findnr(fnum)?;
let b = buf.borrow();
let fname = b.b_fname.clone()?;
let lnum = buflist_findlnum(&b);
Some((fname, lnum))
}
pub fn buflist_new(
ffname_arg: Option<String>,
sfname_arg: Option<String>,
lnum: linenr_T,
flags: i32,
) -> Option<Rc<RefCell<buf_T>>> {
let ffname = ffname_arg;
let sfname = sfname_arg;
let buf = Rc::new(RefCell::new(buf_T::default()));
{
let mut b = buf.borrow_mut();
b.b_vars = Some(tv_dict_alloc());
b.changedtick = 0;
if let Some(ref f) = ffname {
b.b_ffname = Some(f.clone());
b.b_sfname = sfname.clone().or_else(|| Some(f.clone()));
}
let last = lastbuf.with(|l| l.borrow().clone());
match last {
None => {
b.b_prev = None;
firstbuf.with(|f| *f.borrow_mut() = Some(buf.clone()));
}
Some(ref last_rc) => {
last_rc.borrow_mut().b_next = Some(buf.clone());
b.b_prev = Some(Rc::downgrade(last_rc));
}
}
lastbuf.with(|l| *l.borrow_mut() = Some(buf.clone()));
let n = top_file_num.with(|t| {
let v = t.get();
t.set(v + 1);
v
});
b.handle = n;
b.b_fname = b.b_sfname.clone();
b.b_flags = BF_CHECK_RO | BF_NEVERLOADED;
if flags & BLN_DUMMY != 0 {
b.b_flags |= BF_DUMMY;
}
b.b_p_bl = if flags & BLN_LISTED != 0 { 1 } else { 0 };
b.b_p_ma = 1;
let _ = lnum;
}
Some(buf)
}
pub fn buflist_add(fname: Option<String>, flags: i32) -> i32 {
match buflist_new(fname, None, 0, flags) {
Some(buf) => buf.borrow().handle,
None => 0,
}
}
pub fn bt_prompt(buf: &buf_T) -> bool {
buf.b_p_bt.as_bytes().first() == Some(&b'p')
}
pub fn bt_normal(buf: &buf_T) -> bool {
buf.b_p_bt.is_empty()
}
pub fn bt_quickfix(buf: &buf_T) -> bool {
buf.b_p_bt.as_bytes().first() == Some(&b'q')
}
pub fn bt_nofilename(buf: &buf_T) -> bool {
let bt = buf.b_p_bt.as_bytes();
(bt.first() == Some(&b'n') && bt.get(2) == Some(&b'f'))
|| bt.first() == Some(&b'a')
|| buf.terminal
|| bt.first() == Some(&b'p')
}
#[cfg(test)]
mod tests {
use super::*;
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 linenr_T;
}
#[test]
fn buflist_new_links_and_numbers() {
reset();
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();
assert_eq!(a.borrow().handle, 1);
assert_eq!(b.borrow().handle, 2);
assert_eq!(a.borrow().b_next.as_ref().unwrap().borrow().handle, 2);
assert_eq!(
b.borrow()
.b_prev
.as_ref()
.unwrap()
.upgrade()
.unwrap()
.borrow()
.handle,
1
);
assert_eq!(a.borrow().b_p_bl, 1);
}
#[test]
fn buflist_findnr_and_name() {
reset();
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();
assert!(Rc::ptr_eq(&buflist_findnr(2).unwrap(), &b));
assert!(buflist_findnr(99).is_none());
assert!(Rc::ptr_eq(&buflist_findname("/tmp/b").unwrap(), &b));
let (fname, lnum) = buflist_name_nr(1).unwrap();
assert_eq!(fname, "/tmp/a");
assert_eq!(lnum, 1);
}
#[test]
fn ml_get_and_line_count() {
reset();
let buf = buflist_new(Some("/tmp/c".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["first", "second", "third"]);
{
let mut b = buf.borrow_mut();
assert_eq!(b.b_ml.ml_line_count, 3);
assert_eq!(ml_get_buf(&mut b, 1), "first");
assert_eq!(ml_get_buf(&mut b, 3), "third");
assert_eq!(ml_get_buf_len(&mut b, 2), "second".len() as colnr_T);
assert_eq!(ml_get_buf(&mut b, 9), "???");
}
}
#[test]
fn ml_append_replace_delete() {
reset();
let buf = buflist_new(Some("/tmp/d".into()), None, 0, BLN_LISTED).unwrap();
load(&buf, &["one", "two"]);
curbuf.with(|c| *c.borrow_mut() = Some(buf.clone()));
assert_eq!(ml_append(2, "three", 0, false), OK);
assert_eq!(ml_replace(1, "ONE", true), OK);
assert_eq!(ml_delete(2), OK);
{
let mut b = buf.borrow_mut();
assert_eq!(b.b_ml.ml_line_count, 2);
assert_eq!(ml_get_buf(&mut b, 1), "ONE");
assert_eq!(ml_get_buf(&mut b, 2), "three");
}
assert_eq!(ml_delete(9), FAIL);
}
#[test]
fn buftype_predicates() {
let mut b = buf_T::default();
assert!(bt_normal(&b));
b.b_p_bt = "prompt".into();
assert!(bt_prompt(&b));
assert!(bt_nofilename(&b));
b.b_p_bt = "quickfix".into();
assert!(bt_quickfix(&b));
assert!(!bt_normal(&b));
}
}