use crate::clipboard;
#[derive(Clone, Debug, Default)]
pub struct RegisterValue {
pub text: String,
pub linewise: bool,
}
#[derive(Clone, Debug)]
pub struct Registers {
unnamed: Option<RegisterValue>,
named: [Option<RegisterValue>; 26],
pub active: Option<char>,
}
impl Default for Registers {
fn default() -> Self {
Self {
unnamed: None,
named: std::array::from_fn(|_| None),
active: None,
}
}
}
impl Registers {
pub fn new() -> Self {
Self::default()
}
pub fn select(&mut self, name: char) -> bool {
let c = name;
if c.is_ascii_lowercase() || c == '+' || c == '*' || c == '"' {
self.active = Some(c);
true
} else if c.is_ascii_uppercase() {
self.active = Some(c);
true
} else {
false
}
}
pub fn clear_active(&mut self) {
self.active = None;
}
pub fn active_label(&self) -> String {
match self.active {
Some(c) => format!("\"{}", c),
None => "\"".into(),
}
}
pub fn store(&mut self, text: String, linewise: bool) {
let val = RegisterValue { text, linewise };
let target = self.active.take().unwrap_or('"');
match target {
'"' => {
let _ = clipboard::copy(&val.text);
self.unnamed = Some(val);
}
'+' | '*' => {
let _ = clipboard::copy(&val.text);
self.unnamed = Some(val);
}
c if c.is_ascii_lowercase() => {
let idx = (c as u8 - b'a') as usize;
self.named[idx] = Some(val.clone());
let _ = clipboard::copy(&val.text);
self.unnamed = Some(val);
}
c if c.is_ascii_uppercase() => {
let idx = (c as u8 - b'A') as usize;
let mut merged = self.named[idx].clone().unwrap_or_default();
if merged.text.is_empty() {
merged = val.clone();
} else if merged.linewise || val.linewise {
if !merged.text.ends_with('\n') {
merged.text.push('\n');
}
merged.text.push_str(val.text.trim_start_matches('\n'));
merged.linewise = true;
} else {
merged.text.push_str(&val.text);
}
self.named[idx] = Some(merged.clone());
let _ = clipboard::copy(&merged.text);
self.unnamed = Some(merged);
}
_ => {
let _ = clipboard::copy(&val.text);
self.unnamed = Some(val);
}
}
}
pub fn load_for_put(&mut self) -> Option<RegisterValue> {
let target = self.active.take().unwrap_or('"');
match target {
'"' => Self::load_unnamed_or_system(&self.unnamed),
'+' | '*' => clipboard::paste().map(|text| {
let linewise = text.ends_with('\n') || text.lines().count() > 1;
RegisterValue { text, linewise }
}),
c if c.is_ascii_lowercase() || c.is_ascii_uppercase() => {
let idx = (c.to_ascii_lowercase() as u8 - b'a') as usize;
self.named[idx].clone()
}
_ => Self::load_unnamed_or_system(&self.unnamed),
}
}
fn load_unnamed_or_system(unnamed: &Option<RegisterValue>) -> Option<RegisterValue> {
if let Some(sys) = clipboard::paste() {
if !sys.is_empty() {
let linewise = sys.ends_with('\n') || sys.lines().count() > 1;
return Some(RegisterValue {
text: sys,
linewise,
});
}
}
unnamed.clone()
}
pub fn unnamed_text(&self) -> Option<&str> {
self.unnamed.as_ref().map(|v| v.text.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn named_yank_and_put() {
let mut r = Registers::new();
r.select('a');
r.store("hello".into(), false);
assert_eq!(r.unnamed_text(), Some("hello"));
r.select('a');
let v = r.load_for_put().unwrap();
assert_eq!(v.text, "hello");
assert!(!v.linewise);
}
#[test]
fn append_register() {
let mut r = Registers::new();
r.select('a');
r.store("foo".into(), false);
r.select('A');
r.store("bar".into(), false);
r.select('a');
assert_eq!(r.load_for_put().unwrap().text, "foobar");
}
}