use std::collections::HashMap;
use bitflags::bitflags;
#[cfg(feature = "clipboard")]
use arboard::{Clipboard, Get, ImageData, Set};
#[cfg(all(feature = "clipboard", target_os = "linux"))]
use arboard::{GetExtLinux, LinuxClipboardKind, SetExtLinux};
#[cfg(feature = "clipboard")]
use std::cell::{RefCell, RefMut};
use crate::editing::rope::EditRope;
use crate::prelude::Register;
use crate::prelude::TargetShape::{self, BlockWise, CharWise, LineWise};
#[cfg(all(feature = "clipboard", target_os = "linux"))]
mod clipboard {
use super::*;
pub fn set_primary(clipboard: &mut Clipboard) -> Set<'_> {
clipboard.set().clipboard(LinuxClipboardKind::Primary)
}
pub fn set_clipboard(clipboard: &mut Clipboard) -> Set<'_> {
clipboard.set().clipboard(LinuxClipboardKind::Clipboard)
}
pub fn get_primary(clipboard: &mut Clipboard) -> Get<'_> {
clipboard.get().clipboard(LinuxClipboardKind::Primary)
}
pub fn get_clipboard(clipboard: &mut Clipboard) -> Get<'_> {
clipboard.get().clipboard(LinuxClipboardKind::Clipboard)
}
}
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
mod clipboard {
use super::*;
pub fn set_primary(clipboard: &mut Clipboard) -> Set<'_> {
clipboard.set()
}
pub fn set_clipboard(clipboard: &mut Clipboard) -> Set<'_> {
clipboard.set()
}
pub fn get_primary(clipboard: &mut Clipboard) -> Get<'_> {
clipboard.get()
}
pub fn get_clipboard(clipboard: &mut Clipboard) -> Get<'_> {
clipboard.get()
}
}
#[cfg(feature = "clipboard")]
use self::clipboard::*;
bitflags! {
pub struct RegisterPutFlags: u32 {
const NONE = 0b00000000;
const APPEND = 0b00000001;
const DELETE = 0b00000010;
const NOTEXT = 0b00000100;
}
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum RegisterError {
#[error("Clipboard contains image instead of text")]
#[cfg(feature = "clipboard")]
ClipboardImage(ImageData<'static>),
#[error("No macro previously executed")]
NoLastMacro,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RegisterCell {
pub shape: TargetShape,
pub value: EditRope,
}
pub struct RegisterStore {
altbufname: RegisterCell,
curbufname: RegisterCell,
last_command: RegisterCell,
last_inserted: RegisterCell,
last_search: RegisterCell,
last_yanked: RegisterCell,
last_deleted: Vec<RegisterCell>,
last_macro: Option<Register>,
small_delete: RegisterCell,
unnamed: RegisterCell,
unnamed_macro: RegisterCell,
named: HashMap<char, RegisterCell>,
#[cfg(feature = "clipboard")]
clipboard: Option<RefCell<Clipboard>>,
}
impl RegisterCell {
pub fn new(shape: TargetShape, value: EditRope) -> Self {
RegisterCell { shape, value }
}
pub fn merge(&self, other: &RegisterCell) -> RegisterCell {
match (self.shape, other.shape) {
(CharWise, CharWise) | (LineWise, LineWise) | (CharWise, BlockWise) => {
let text = self.value.clone() + other.value.clone();
RegisterCell::new(self.shape, text)
},
(BlockWise, BlockWise | CharWise) => {
let text = self.value.clone() + EditRope::from("\n") + other.value.clone();
RegisterCell::new(self.shape, text)
},
(BlockWise | CharWise, LineWise) => {
let text = self.value.clone() + EditRope::from("\n") + other.value.clone();
RegisterCell::new(other.shape, text)
},
(LineWise, BlockWise | CharWise) => {
let text = self.value.clone() + other.value.clone() + EditRope::from("\n");
RegisterCell::new(self.shape, text)
},
}
}
}
impl Default for RegisterCell {
fn default() -> RegisterCell {
RegisterCell::new(TargetShape::CharWise, EditRope::from(""))
}
}
impl From<&str> for RegisterCell {
fn from(s: &str) -> RegisterCell {
RegisterCell::new(TargetShape::CharWise, EditRope::from(s))
}
}
impl From<EditRope> for RegisterCell {
fn from(s: EditRope) -> RegisterCell {
RegisterCell::new(TargetShape::CharWise, s)
}
}
impl From<(TargetShape, &str)> for RegisterCell {
fn from(c: (TargetShape, &str)) -> RegisterCell {
RegisterCell::new(c.0, EditRope::from(c.1))
}
}
impl RegisterStore {
fn new() -> Self {
RegisterStore {
altbufname: RegisterCell::default(),
curbufname: RegisterCell::default(),
last_command: RegisterCell::default(),
last_inserted: RegisterCell::default(),
last_search: RegisterCell::default(),
last_yanked: RegisterCell::default(),
last_deleted: vec![RegisterCell::default(); 9],
last_macro: None,
small_delete: RegisterCell::default(),
unnamed: RegisterCell::default(),
unnamed_macro: RegisterCell::default(),
named: HashMap::new(),
#[cfg(feature = "clipboard")]
clipboard: Clipboard::new().ok().map(RefCell::new),
}
}
#[cfg(feature = "clipboard")]
fn clipboard(&self) -> Option<RefMut<'_, Clipboard>> {
self.clipboard.as_ref().map(RefCell::borrow_mut)
}
fn _push_deleted(&mut self, cell: RegisterCell) {
if cell.value.get_lines() < 1 {
self.small_delete = cell;
} else {
self.last_deleted.insert(0, cell);
self.last_deleted.truncate(9);
}
}
pub fn get(&self, reg: &Register) -> Result<RegisterCell, RegisterError> {
let reg = match reg {
Register::Unnamed => self.unnamed.clone(),
Register::UnnamedMacro => self.unnamed_macro.clone(),
Register::UnnamedCursorGroup => RegisterCell::default(),
Register::RecentlyDeleted(off) => {
self.last_deleted.get(*off).cloned().unwrap_or_default()
},
Register::SmallDelete => self.small_delete.clone(),
Register::Named(name) => self.named.get(name).cloned().unwrap_or_default(),
Register::AltBufName => self.altbufname.clone(),
Register::LastSearch => self.last_search.clone(),
Register::LastYanked => self.last_yanked.clone(),
Register::SelectionPrimary => {
#[cfg(feature = "clipboard")]
if let Some(ref mut clipboard) = self.clipboard() {
if let Ok(image) = get_primary(clipboard).image() {
return Err(RegisterError::ClipboardImage(image.to_owned_img()));
}
if let Ok(text) = get_primary(clipboard).text() {
RegisterCell::from(EditRope::from(text))
} else {
RegisterCell::default()
}
} else {
RegisterCell::default()
}
#[cfg(not(feature = "clipboard"))]
RegisterCell::default()
},
Register::SelectionClipboard => {
#[cfg(feature = "clipboard")]
if let Some(ref mut clipboard) = self.clipboard() {
if let Ok(image) = get_clipboard(clipboard).image() {
return Err(RegisterError::ClipboardImage(image));
}
if let Ok(text) = get_clipboard(clipboard).text() {
RegisterCell::from(EditRope::from(text))
} else {
RegisterCell::default()
}
} else {
RegisterCell::default()
}
#[cfg(not(feature = "clipboard"))]
RegisterCell::default()
},
Register::CurBufName => self.curbufname.clone(),
Register::LastCommand => self.last_command.clone(),
Register::LastInserted => self.last_inserted.clone(),
Register::Blackhole => RegisterCell::default(),
};
Ok(reg)
}
pub fn put(
&mut self,
reg: &Register,
mut cell: RegisterCell,
flags: RegisterPutFlags,
) -> Result<(), RegisterError> {
if flags.contains(RegisterPutFlags::APPEND) {
cell = self.get(reg)?.merge(&cell)
}
let unnamed = match reg {
Register::Blackhole => return Ok(()),
Register::UnnamedCursorGroup => return Ok(()),
Register::Unnamed => {
if flags.contains(RegisterPutFlags::DELETE) {
self._push_deleted(cell.clone());
} else {
self.last_yanked = cell.clone();
}
cell
},
Register::UnnamedMacro => {
self.unnamed_macro = cell.clone();
cell
},
Register::Named(name) => {
self.named.insert(*name, cell.clone());
cell
},
Register::RecentlyDeleted(off) => {
if let Some(elem) = self.last_deleted.get_mut(*off) {
*elem = cell.clone();
}
cell
},
Register::SmallDelete => {
self.small_delete = cell.clone();
cell
},
Register::AltBufName => {
self.altbufname = cell.clone();
cell
},
Register::LastYanked => {
self.last_yanked = cell.clone();
cell
},
Register::SelectionPrimary => {
#[cfg(feature = "clipboard")]
if let Some(ref mut clipboard) = self.clipboard() {
let op = set_primary(clipboard);
let _ = op.text(&cell.value);
}
cell
},
Register::SelectionClipboard => {
#[cfg(feature = "clipboard")]
if let Some(ref mut clipboard) = self.clipboard() {
let op = set_clipboard(clipboard);
let _ = op.text(&cell.value);
}
cell
},
Register::CurBufName => cell,
Register::LastCommand => cell,
Register::LastInserted => cell,
Register::LastSearch => cell,
};
if !flags.contains(RegisterPutFlags::NOTEXT) {
self.unnamed = unnamed;
}
Ok(())
}
pub fn get_macro(&mut self, reg: Register) -> Result<EditRope, RegisterError> {
let res = self.get(®)?.value;
self.last_macro = Some(reg);
return Ok(res);
}
pub fn get_last_macro(&self) -> Result<EditRope, RegisterError> {
if let Some(ref reg) = self.last_macro {
return Ok(self.get(reg)?.value);
} else {
return Err(RegisterError::NoLastMacro);
}
}
pub(super) fn set_last_cmd<T: Into<EditRope>>(&mut self, rope: T) {
self.last_command = RegisterCell::from(rope.into());
}
pub(super) fn set_last_search<T: Into<EditRope>>(&mut self, rope: T) {
self.last_search = RegisterCell::from(rope.into());
}
}
impl Default for RegisterStore {
fn default() -> RegisterStore {
RegisterStore::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cell_merge() {
let a = RegisterCell::new(CharWise, EditRope::from("a"));
let b = RegisterCell::new(BlockWise, EditRope::from("1\n2"));
let c = RegisterCell::new(LineWise, EditRope::from("q\nr\ns\n"));
assert_eq!(a.merge(&a), RegisterCell::new(CharWise, EditRope::from("aa")));
assert_eq!(a.merge(&b), RegisterCell::new(CharWise, EditRope::from("a1\n2")));
assert_eq!(a.merge(&c), RegisterCell::new(LineWise, EditRope::from("a\nq\nr\ns\n")));
assert_eq!(b.merge(&a), RegisterCell::new(BlockWise, EditRope::from("1\n2\na")));
assert_eq!(b.merge(&b), RegisterCell::new(BlockWise, EditRope::from("1\n2\n1\n2")));
assert_eq!(b.merge(&c), RegisterCell::new(LineWise, EditRope::from("1\n2\nq\nr\ns\n")));
assert_eq!(c.merge(&a), RegisterCell::new(LineWise, EditRope::from("q\nr\ns\na\n")));
assert_eq!(c.merge(&b), RegisterCell::new(LineWise, EditRope::from("q\nr\ns\n1\n2\n")));
assert_eq!(c.merge(&c), RegisterCell::new(LineWise, EditRope::from("q\nr\ns\nq\nr\ns\n")));
}
}