#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types)]
use std::cell::{Cell, RefCell};
type colnr_T = i32;
const DELETION_REGISTER: usize = 36;
#[allow(dead_code)]
const NUM_SAVED_REGISTERS: usize = 37;
const STAR_REGISTER: usize = 37;
const PLUS_REGISTER: usize = 38;
const NUM_REGISTERS: usize = 39;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MotionType {
CharWise,
LineWise,
BlockWise(colnr_T),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum yreg_mode_t {
YREG_PASTE,
YREG_YANK,
YREG_PUT,
}
use yreg_mode_t::*;
struct yankreg_T {
y_array: Option<Vec<String>>,
y_size: usize,
y_type: MotionType,
y_width: colnr_T,
timestamp: i64,
}
impl Default for yankreg_T {
fn default() -> Self {
yankreg_T {
y_array: None,
y_size: 0,
y_type: MotionType::CharWise,
y_width: 0,
timestamp: 0,
}
}
}
thread_local! {
static Y_REGS: RefCell<Vec<yankreg_T>> =
RefCell::new((0..NUM_REGISTERS).map(|_| yankreg_T::default()).collect());
static Y_PREVIOUS: Cell<Option<usize>> = const { Cell::new(None) };
}
fn op_reg_index(regname: i32) -> i32 {
if regname > 0 && (regname as u8 as char).is_ascii_digit() {
regname - '0' as i32 } else if regname > 0 && (regname as u8 as char).is_ascii_lowercase() {
(regname - 'a' as i32) + 10 } else if regname > 0 && (regname as u8 as char).is_ascii_uppercase() {
(regname - 'A' as i32) + 10 } else if regname == '-' as i32 {
DELETION_REGISTER as i32
} else if regname == '*' as i32 {
STAR_REGISTER as i32
} else if regname == '+' as i32 {
PLUS_REGISTER as i32
} else {
-1
}
}
fn is_append_register(regname: i32) -> bool {
regname > 0 && (regname as u8 as char).is_ascii_uppercase()
}
#[allow(dead_code)]
fn get_register_name(num: i32) -> i32 {
if num == -1 {
'"' as i32
} else if num < 10 {
num + '0' as i32
} else if num == DELETION_REGISTER as i32 {
'-' as i32
} else if num == STAR_REGISTER as i32 {
'*' as i32
} else if num == PLUS_REGISTER as i32 {
'+' as i32
} else {
num + 'a' as i32 - 10
}
}
#[allow(dead_code)]
fn reg_empty(reg: &yankreg_T) -> bool {
reg.y_array.is_none()
|| reg.y_size == 0
|| (reg.y_size == 1
&& reg.y_type == MotionType::CharWise
&& reg
.y_array
.as_ref()
.map(|a| a[0].is_empty())
.unwrap_or(true))
}
#[allow(dead_code)]
fn get_y_register(reg: usize) -> usize {
reg }
#[allow(dead_code)]
fn get_y_previous() -> Option<usize> {
Y_PREVIOUS.with(|p| p.get())
}
fn valid_yank_reg(regname: i32, writing: bool) -> bool {
(regname > 0 && regname < 256 && (regname as u8 as char).is_ascii_alphanumeric())
|| (!writing && b"/#.%:=".contains(&(regname as u8)) && regname > 0 && regname < 256)
|| regname == '"' as i32
|| regname == '-' as i32
|| regname == '_' as i32
|| regname == '*' as i32
|| regname == '+' as i32
}
#[allow(dead_code)]
fn op_reg_get(name: char) -> Option<usize> {
let i = op_reg_index(name as i32); if i == -1 {
return None; }
Some(i as usize) }
fn get_yank_register(regname: i32, mode: yreg_mode_t) -> usize {
if mode != YREG_YANK
&& (regname == 0 || regname == '"' as i32 || regname == '*' as i32 || regname == '+' as i32)
{
if let Some(prev) = Y_PREVIOUS.with(|p| p.get()) {
return prev;
}
}
let mut i = op_reg_index(regname);
if i == -1 {
i = 0;
}
let i = i as usize;
if mode == YREG_YANK {
Y_PREVIOUS.with(|p| p.set(Some(i)));
}
i
}
fn free_register(reg: &mut yankreg_T) {
reg.y_array = None;
reg.y_size = 0;
}
pub fn format_reg_type(reg_type: MotionType, reg_width: colnr_T) -> String {
match reg_type {
MotionType::LineWise => "V".to_string(),
MotionType::CharWise => "v".to_string(),
MotionType::BlockWise(_) => format!("\u{16}{}", reg_width + 1),
}
}
pub fn get_reg_type(regname: char) -> (MotionType, colnr_T) {
let regname = regname as i32;
match regname {
0x25 | 0x23 | 0x3d | 0x3a | 0x2f | 0x2e | 0x06 | 0x10 | 0x17 | 0x01 | 0x5f => {
return (MotionType::CharWise, 0);
}
_ => {}
}
if regname != 0 && !valid_yank_reg(regname, false) {
return (MotionType::CharWise, 0); }
let i = get_yank_register(regname, YREG_PASTE);
Y_REGS.with(|r| {
let regs = r.borrow();
let reg = ®s[i];
if reg.y_array.is_some() {
if reg.y_type == MotionType::BlockWise(reg.y_width) {
return (reg.y_type, reg.y_width);
}
return (reg.y_type, 0);
}
(MotionType::CharWise, 0) })
}
fn get_reg_wrap_one_line(s: &str) -> Vec<String> {
vec![s.to_string()]
}
pub fn get_reg_contents(regname: char) -> Option<Vec<String>> {
let mut regname = regname as i32;
if regname == '=' as i32 {
return None;
}
if regname == '@' as i32 {
regname = '"' as i32;
}
if regname != 0 && !valid_yank_reg(regname, false) {
return None;
}
if regname == '_' as i32 {
return Some(get_reg_wrap_one_line(""));
}
let i = get_yank_register(regname, YREG_PUT);
Y_REGS.with(|r| r.borrow()[i].y_array.clone())
}
fn init_write_reg(name: i32, must_append: bool) -> Option<(usize, Option<usize>)> {
if !valid_yank_reg(name, true) {
return None;
}
let old_y_previous = Y_PREVIOUS.with(|p| p.get());
let reg = get_yank_register(name, YREG_YANK);
if !is_append_register(name) && !must_append {
Y_REGS.with(|r| free_register(&mut r.borrow_mut()[reg]));
}
Some((reg, old_y_previous))
}
enum reg_input<'a> {
Str(&'a [u8]),
List(&'a [String]),
}
fn str_to_reg(
y_ptr: &mut yankreg_T,
yank_type: Option<MotionType>,
input: reg_input,
blocklen: colnr_T,
) {
if y_ptr.y_array.is_none() {
y_ptr.y_size = 0;
}
let mut yank_type = match yank_type {
Some(t) => t,
None => {
let linewise = match &input {
reg_input::List(_) => true,
reg_input::Str(s) => {
let len = s.len();
len > 0 && (s[len - 1] == b'\n' || s[len - 1] == b'\r')
}
};
if linewise {
MotionType::LineWise
} else {
MotionType::CharWise
}
}
};
let is_block = matches!(yank_type, MotionType::BlockWise(_));
let mut extraline = false; let mut append = false;
match &input {
reg_input::List(_) => {}
reg_input::Str(s) => {
let len = s.len();
if yank_type == MotionType::CharWise || len == 0 || s[len - 1] != b'\n' {
extraline = true; }
if y_ptr.y_size > 0 && y_ptr.y_type == MotionType::CharWise {
append = true;
}
}
}
let mut pp: Vec<String> = y_ptr.y_array.take().unwrap_or_default();
let mut lnum = y_ptr.y_size;
let mut maxlen: usize = 0;
match input {
reg_input::List(strings) => {
for ss in strings {
pp.push(ss.clone());
if is_block {
let charlen = ss.chars().count();
maxlen = maxlen.max(charlen);
}
lnum += 1;
}
}
reg_input::Str(s) => {
let end = s.len();
let mut start = 0usize;
while start < end + extraline as usize {
let mut charlen: i32 = 0;
let mut line_end = start;
while line_end < end {
if s[line_end] == b'\n' {
break;
}
if is_block {
charlen += 1;
}
if s[line_end] == 0 {
line_end += 1; } else {
let b = s[line_end];
let n = if b < 0x80 {
1
} else if b >> 5 == 0b110 {
2
} else if b >> 4 == 0b1110 {
3
} else if b >> 3 == 0b11110 {
4
} else {
1
};
line_end += n.min(end - line_end).max(1);
}
}
maxlen = maxlen.max(charlen as usize);
let mut buf: Vec<u8> = if append {
lnum -= 1;
std::mem::take(&mut pp[lnum]).into_bytes()
} else {
Vec::new()
};
buf.extend_from_slice(&s[start..line_end]);
for b in buf.iter_mut() {
if *b == 0 {
*b = b'\n';
}
}
let line = String::from_utf8(buf)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
if append {
pp[lnum] = line;
append = false; } else {
pp.push(line);
}
lnum += 1;
start = line_end + 1; }
}
}
if lnum == 0 {
y_ptr.y_array = None;
y_ptr.y_size = 0;
return;
}
if is_block {
y_ptr.y_width = if blocklen == -1 {
maxlen as colnr_T - 1
} else {
blocklen
};
yank_type = MotionType::BlockWise(y_ptr.y_width);
} else {
y_ptr.y_width = 0;
}
y_ptr.y_type = yank_type;
y_ptr.y_size = lnum;
y_ptr.timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
y_ptr.y_array = Some(pp);
}
fn finish_write_reg(name: i32, old_y_previous: Option<usize>) {
if name != '"' as i32 {
Y_PREVIOUS.with(|p| p.set(old_y_previous));
}
}
fn write_reg_contents_ex(
name: i32,
str: &[u8],
must_append: bool,
yank_type: Option<MotionType>,
block_len: colnr_T,
) {
if name == '/' as i32 || name == '#' as i32 || name == '=' as i32 {
return;
}
if name == '_' as i32 {
return;
}
let (reg, old_y_previous) = match init_write_reg(name, must_append) {
Some(v) => v,
None => return,
};
Y_REGS.with(|r| {
str_to_reg(
&mut r.borrow_mut()[reg],
yank_type,
reg_input::Str(str),
block_len,
)
});
finish_write_reg(name, old_y_previous);
}
pub fn write_reg_contents(name: char, value: &str, mtype: MotionType, append: bool) {
let (yt, block_len) = match mtype {
MotionType::BlockWise(w) => (Some(MotionType::BlockWise(w)), w),
other => (Some(other), 0),
};
write_reg_contents_ex(name as i32, value.as_bytes(), append, yt, block_len);
}
pub fn write_reg_contents_lst(name: char, lines: Vec<String>, mtype: MotionType, append: bool) {
let name = name as i32;
let (yt, block_len) = match mtype {
MotionType::BlockWise(w) => (Some(MotionType::BlockWise(w)), w),
other => (Some(other), 0),
};
if name == '/' as i32 || name == '=' as i32 || name == '#' as i32 {
let s = lines.first().cloned().unwrap_or_default();
write_reg_contents_ex(name, s.as_bytes(), append, yt, block_len);
return;
}
if name == '_' as i32 {
return;
}
let (reg, old_y_previous) = match init_write_reg(name, append) {
Some(v) => v,
None => return,
};
Y_REGS.with(|r| {
str_to_reg(
&mut r.borrow_mut()[reg],
yt,
reg_input::List(&lines),
block_len,
)
});
finish_write_reg(name, old_y_previous);
}
pub fn get_yank_type(c: u8, width: colnr_T) -> Option<MotionType> {
match c {
b'v' | b'c' => Some(MotionType::CharWise),
b'V' | b'l' => Some(MotionType::LineWise),
b'b' | 0x16 => Some(MotionType::BlockWise(width)), _ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reset() {
Y_REGS
.with(|r| *r.borrow_mut() = (0..NUM_REGISTERS).map(|_| yankreg_T::default()).collect());
Y_PREVIOUS.with(|p| p.set(None));
}
#[test]
fn charwise_and_linewise() {
reset();
write_reg_contents('a', "hello", MotionType::CharWise, false);
assert_eq!(get_reg_contents('a'), Some(vec!["hello".to_string()]));
assert_eq!(format_reg_type(get_reg_type('a').0, 0), "v");
assert_eq!(get_reg_contents('q'), None);
write_reg_contents_lst(
'b',
vec!["x".into(), "y".into()],
MotionType::LineWise,
false,
);
assert_eq!(
get_reg_contents('b'),
Some(vec!["x".to_string(), "y".to_string()])
);
assert_eq!(format_reg_type(get_reg_type('b').0, 0), "V");
write_reg_contents_lst('b', vec!["z".into()], MotionType::LineWise, true);
assert_eq!(
get_reg_contents('b'),
Some(vec!["x".to_string(), "y".to_string(), "z".to_string()])
);
}
#[test]
fn let_register_roundtrip() {
reset();
write_reg_contents_lst('a', vec!["x".into()], MotionType::CharWise, false);
assert_eq!(get_reg_contents('a'), Some(vec!["x".to_string()]));
assert_eq!(get_reg_type('a').0, MotionType::CharWise);
}
#[test]
fn charwise_append_merges() {
reset();
write_reg_contents('a', "foo", MotionType::CharWise, false);
write_reg_contents('a', "bar", MotionType::CharWise, true);
assert_eq!(get_reg_contents('a'), Some(vec!["foobar".to_string()]));
}
#[test]
fn non_append_replaces() {
reset();
write_reg_contents_lst(
'c',
vec!["one".into(), "two".into()],
MotionType::LineWise,
false,
);
write_reg_contents_lst('c', vec!["only".into()], MotionType::LineWise, false);
assert_eq!(get_reg_contents('c'), Some(vec!["only".to_string()]));
}
#[test]
fn uppercase_appends() {
reset();
write_reg_contents_lst('d', vec!["a".into()], MotionType::LineWise, false);
write_reg_contents_lst('D', vec!["b".into()], MotionType::LineWise, false);
assert_eq!(
get_reg_contents('d'),
Some(vec!["a".to_string(), "b".to_string()])
);
}
#[test]
fn blockwise_regtype() {
reset();
write_reg_contents_lst(
'e',
vec!["ab".into(), "cd".into()],
MotionType::BlockWise(1),
false,
);
let (t, w) = get_reg_type('e');
assert_eq!(t, MotionType::BlockWise(1));
assert_eq!(format_reg_type(t, w), "\u{16}2");
}
#[test]
fn black_hole_discards() {
reset();
write_reg_contents_lst('_', vec!["gone".into()], MotionType::CharWise, false);
assert_eq!(get_reg_contents('_'), Some(vec![String::new()]));
assert_eq!(get_reg_contents('0'), None);
}
#[test]
fn op_reg_get_index() {
reset();
assert_eq!(op_reg_get('a'), Some(10)); assert_eq!(op_reg_get('0'), Some(0));
assert_eq!(op_reg_get('-'), Some(DELETION_REGISTER));
assert_eq!(op_reg_get('*'), Some(STAR_REGISTER));
assert_eq!(op_reg_get('"'), None); assert_eq!(op_reg_get('_'), None); assert_eq!(get_y_register(10), 10);
}
#[test]
fn wrap_one_line() {
reset();
assert_eq!(get_reg_wrap_one_line("x"), vec!["x".to_string()]);
assert_eq!(get_reg_contents('_'), Some(vec![String::new()]));
}
#[test]
fn unnamed_alias() {
reset();
write_reg_contents_lst('"', vec!["u".into()], MotionType::CharWise, false);
assert_eq!(get_reg_contents('@'), Some(vec!["u".to_string()]));
assert_eq!(get_reg_contents('"'), Some(vec!["u".to_string()]));
}
}