Skip to content

Commit

Permalink
feat: add xml fragment capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
eliias committed Feb 8, 2023
1 parent bfa95b5 commit 836f77c
Show file tree
Hide file tree
Showing 12 changed files with 612 additions and 48 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ext/yrb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ repository = "https://github.com/y-crdt/yrb"

[dependencies]
lib0 = "0.16.2" # must match yrs version
magnus = { git = "https://github.com/matsadler/magnus", rev = "ab982c3643421b38f3293f1fe014aa373abfd6dc" }
magnus = { git = "https://github.com/matsadler/magnus", rev = "2c2024920a403daadbe23fe63270440dfac86288" }
thiserror = "1.0.38"
yrs = "0.16.2"

[dev-dependencies]
magnus = { git = "https://github.com/matsadler/magnus", rev = "ab982c3643421b38f3293f1fe014aa373abfd6dc", features = ["embed"] }
magnus = { git = "https://github.com/matsadler/magnus", rev = "2c2024920a403daadbe23fe63270440dfac86288", features = ["embed"] }

[lib]
name = "yrb"
Expand Down
72 changes: 72 additions & 0 deletions ext/yrb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::ymap::YMap;
use crate::ytext::YText;
use crate::ytransaction::YTransaction;
use crate::yxml_element::YXmlElement;
use crate::yxml_fragment::YXmlFragment;
use crate::yxml_text::YXmlText;
use magnus::{define_module, function, method, Error, Module, Object};

Expand Down Expand Up @@ -296,6 +297,12 @@ fn init() -> Result<(), Error> {
method!(YXmlElement::yxml_element_insert_text, 3),
)
.expect("cannot define private method: yxml_element_insert_text");
yxml_element
.define_private_method(
"yxml_element_len",
method!(YXmlElement::yxml_element_len, 1),
)
.expect("cannot define private method: yxml_element_len");
yxml_element
.define_private_method(
"yxml_element_next_sibling",
Expand Down Expand Up @@ -387,6 +394,71 @@ fn init() -> Result<(), Error> {
)
.expect("cannot define private method: yxml_element_unobserve");

let yxml_fragment = module
.define_class("XMLFragment", Default::default())
.expect("cannot define class: Y::XMLFragment");

yxml_fragment
.define_private_method(
"yxml_fragment_first_child",
method!(YXmlFragment::yxml_fragment_first_child, 0),
)
.expect("cannot define private method: yxml_fragment_first_child");
yxml_fragment
.define_private_method(
"yxml_fragment_get",
method!(YXmlFragment::yxml_fragment_get, 2),
)
.expect("cannot define private method: yxml_fragment_get");
yxml_fragment
.define_private_method(
"yxml_fragment_insert",
method!(YXmlFragment::yxml_fragment_insert, 3),
)
.expect("cannot define private method: yxml_fragment_insert");
yxml_fragment
.define_private_method(
"yxml_fragment_len",
method!(YXmlFragment::yxml_fragment_len, 1),
)
.expect("cannot define private method: yxml_fragment_len");
yxml_fragment
.define_private_method(
"yxml_fragment_parent",
method!(YXmlFragment::yxml_fragment_parent, 0),
)
.expect("cannot define private method: yxml_fragment_parent");
yxml_fragment
.define_private_method(
"yxml_fragment_push_back",
method!(YXmlFragment::yxml_fragment_push_back, 2),
)
.expect("cannot define private method: yxml_fragment_push_back");
yxml_fragment
.define_private_method(
"yxml_fragment_push_front",
method!(YXmlFragment::yxml_fragment_push_front, 2),
)
.expect("cannot define private method: yxml_fragment_push_front");
yxml_fragment
.define_private_method(
"yxml_fragment_remove_range",
method!(YXmlFragment::yxml_fragment_remove_range, 3),
)
.expect("cannot define private method: yxml_fragment_remove_range");
yxml_fragment
.define_private_method(
"yxml_fragment_successors",
method!(YXmlFragment::yxml_fragment_successors, 1),
)
.expect("cannot define private method: yxml_fragment_successors");
yxml_fragment
.define_private_method(
"yxml_fragment_to_s",
method!(YXmlFragment::yxml_fragment_to_s, 1),
)
.expect("cannot define private method: yxml_fragment_to_s");

let yxml_text = module
.define_class("XMLText", Default::default())
.expect("cannot define class Y::XMLText");
Expand Down
23 changes: 12 additions & 11 deletions ext/yrb/src/ydoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use crate::yxml_fragment::YXmlFragment;
use crate::yxml_text::YXmlText;
use crate::YTransaction;
use magnus::block::Proc;
use magnus::exception::runtime_error;
use magnus::{exception, Error, Integer, RArray, Value};
use magnus::{exception::runtime_error, Error, Integer, RArray, Value};
use std::borrow::Borrow;
use std::cell::RefCell;
use yrs::updates::decoder::Decode;
Expand Down Expand Up @@ -41,19 +40,22 @@ impl YDoc {

StateVector::decode_v1(state_vector.borrow())
.map(|sv| tx.encode_diff_v1(&sv))
.map_err(|_e| Error::new(exception::runtime_error(), "cannot encode diff"))
.map_err(|_e| Error::new(runtime_error(), "cannot encode diff"))
}

pub(crate) fn ydoc_get_or_insert_array(&self, name: String) -> YArray {
self.0.borrow().get_or_insert_array(name.as_str()).into()
let array_ref = self.0.borrow().get_or_insert_array(name.as_str());
YArray::from(array_ref)
}

pub(crate) fn ydoc_get_or_insert_map(&self, name: String) -> YMap {
self.0.borrow().get_or_insert_map(name.as_str()).into()
let map_ref = self.0.borrow().get_or_insert_map(name.as_str());
YMap::from(map_ref)
}

pub(crate) fn ydoc_get_or_insert_text(&self, name: String) -> YText {
self.0.borrow().get_or_insert_text(name.as_str()).into()
let text_ref = self.0.borrow().get_or_insert_text(name.as_str());
YText::from(text_ref)
}

pub(crate) fn ydoc_get_or_insert_xml_element(&self, name: String) -> YXmlElement {
Expand All @@ -62,14 +64,13 @@ impl YDoc {
}

pub(crate) fn ydoc_get_or_insert_xml_fragment(&self, name: String) -> YXmlFragment {
self.0
.borrow()
.get_or_insert_xml_fragment(name.as_str())
.into()
let xml_fragment_ref = self.0.borrow().get_or_insert_xml_fragment(name.as_str());
YXmlFragment::from(xml_fragment_ref)
}

pub(crate) fn ydoc_get_or_insert_xml_text(&self, name: String) -> YXmlText {
self.0.borrow().get_or_insert_xml_text(name.as_str()).into()
let xml_text_ref = self.0.borrow().get_or_insert_xml_text(name.as_str());
YXmlText::from(xml_text_ref)
}

pub(crate) fn ydoc_transact<'doc>(&self) -> YTransaction {
Expand Down
6 changes: 6 additions & 0 deletions ext/yrb/src/yxml_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ impl YXmlElement {

YXmlText::from(self.0.borrow_mut().insert(tx, index, text))
}
pub(crate) fn yxml_element_len(&self, transaction: &YTransaction) -> u32 {
let mut tx = transaction.transaction();
let tx = tx.as_mut().unwrap();

self.0.borrow().len(tx)
}
pub(crate) fn yxml_element_next_sibling(&self, transaction: &YTransaction) -> Option<Value> {
let tx = transaction.transaction();
let tx = tx.as_ref().unwrap();
Expand Down
117 changes: 115 additions & 2 deletions ext/yrb/src/yxml_fragment.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,126 @@
use crate::ytransaction::YTransaction;
use crate::yxml_element::YXmlElement;
use crate::yxml_text::YXmlText;
use magnus::{RArray, Value};
use std::cell::RefCell;
use yrs::XmlFragmentRef;
use yrs::{GetString, XmlElementPrelim, XmlFragment, XmlFragmentRef, XmlNode};

#[magnus::wrap(class = "Y::XMLFragment")]
pub(crate) struct YXmlFragment(pub(crate) RefCell<XmlFragmentRef>);

/// SAFETY: This is safe because we only access this data when the GVL is held.
unsafe impl Send for YXmlFragment {}

impl YXmlFragment {}
impl YXmlFragment {
pub(crate) fn yxml_fragment_first_child(&self) -> Option<Value> {
self.0.borrow().first_child().map(|node| match node {
XmlNode::Element(element) => Value::from(YXmlElement::from(element)),
XmlNode::Fragment(fragment) => Value::from(YXmlFragment::from(fragment)),
XmlNode::Text(text) => Value::from(YXmlText::from(text)),
})
}

pub(crate) fn yxml_fragment_get(
&self,
transaction: &YTransaction,
index: u32,
) -> Option<Value> {
let tx = transaction.transaction();
let tx = tx.as_ref().unwrap();

self.0.borrow().get(tx, index).map(|node| match node {
XmlNode::Element(element) => Value::from(YXmlElement::from(element)),
XmlNode::Fragment(fragment) => Value::from(YXmlFragment::from(fragment)),
XmlNode::Text(text) => Value::from(YXmlText::from(text)),
})
}

pub(crate) fn yxml_fragment_insert(
&self,
transaction: &YTransaction,
index: u32,
tag: String,
) -> YXmlElement {
let mut tx = transaction.transaction();
let tx = tx.as_mut().unwrap();

let node = XmlElementPrelim::empty(tag);
YXmlElement::from(self.0.borrow_mut().insert(tx, index, node))
}

pub(crate) fn yxml_fragment_len(&self, transaction: &YTransaction) -> u32 {
let tx = transaction.transaction();
let tx = tx.as_ref().unwrap();

self.0.borrow().len(tx)
}

pub(crate) fn yxml_fragment_parent(&self) -> Option<Value> {
self.0.borrow().parent().map(|item| match item {
XmlNode::Element(el) => Value::from(YXmlElement::from(el)),
XmlNode::Fragment(fragment) => Value::from(YXmlFragment::from(fragment)),
XmlNode::Text(text) => Value::from(YXmlText::from(text)),
})
}

pub(crate) fn yxml_fragment_push_back(
&self,
transaction: &YTransaction,
tag: String,
) -> YXmlElement {
let mut tx = transaction.transaction();
let tx = tx.as_mut().unwrap();

let node = XmlElementPrelim::empty(tag);
YXmlElement::from(self.0.borrow_mut().push_back(tx, node))
}

pub(crate) fn yxml_fragment_push_front(
&self,
transaction: &YTransaction,
tag: String,
) -> YXmlElement {
let mut tx = transaction.transaction();
let tx = tx.as_mut().unwrap();

let node = XmlElementPrelim::empty(tag);
YXmlElement::from(self.0.borrow_mut().push_front(tx, node))
}

pub(crate) fn yxml_fragment_remove_range(
&self,
transaction: &YTransaction,
index: u32,
length: u32,
) {
let mut tx = transaction.transaction();
let tx = tx.as_mut().unwrap();

self.0.borrow_mut().remove_range(tx, index, length);
}

pub(crate) fn yxml_fragment_successors(&self, transaction: &YTransaction) -> RArray {
let tx = transaction.transaction();
let tx = tx.as_ref().unwrap();

let fragment = self.0.borrow();

let result = fragment.successors(tx).map(|item| match item {
XmlNode::Element(el) => Value::from(YXmlElement::from(el)),
XmlNode::Fragment(fragment) => Value::from(YXmlFragment::from(fragment)),
XmlNode::Text(text) => Value::from(YXmlText::from(text)),
});

RArray::from_iter(result)
}

pub(crate) fn yxml_fragment_to_s(&self, transaction: &YTransaction) -> String {
let tx = transaction.transaction();
let tx = tx.as_ref().unwrap();

self.0.borrow().get_string(tx)
}
}

impl From<XmlFragmentRef> for YXmlFragment {
fn from(v: XmlFragmentRef) -> Self {
Expand Down
9 changes: 6 additions & 3 deletions lib/y/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ def []=(index, value)
#
# @param value [true|false|Float|Integer|String|::Array|Hash]
# @return [void]
def <<(value)
document.current_transaction { |tx| yarray_push_back(tx, value) }
def <<(value, *values)
document.current_transaction do |tx|
yarray_push_back(tx, value)
values.each { |v| yarray_push_back(tx, v) }
end
end

# Attach listener to array changes
Expand Down Expand Up @@ -218,7 +221,7 @@ def size
# @return [void]
def slice!(*args)
document.current_transaction do |tx| # rubocop:disable Metrics/BlockLength
if args.size.zero?
if args.empty?
raise ArgumentError,
"Provide one of `index`, `range`, `start, length` as arguments"
end
Expand Down
Loading

0 comments on commit 836f77c

Please sign in to comment.