document_pad.rs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. use crate::{
  2. client_document::{
  3. default::initial_delta,
  4. history::{History, UndoResult},
  5. view::{ViewExtensions, RECORD_THRESHOLD},
  6. },
  7. errors::CollaborateError,
  8. };
  9. use lib_ot::{
  10. core::*,
  11. rich_text::{RichTextAttribute, RichTextDelta},
  12. };
  13. use tokio::sync::mpsc;
  14. pub trait InitialDocumentText {
  15. fn initial_delta() -> RichTextDelta;
  16. }
  17. pub struct PlainDoc();
  18. impl InitialDocumentText for PlainDoc {
  19. fn initial_delta() -> RichTextDelta {
  20. RichTextDelta::new()
  21. }
  22. }
  23. pub struct NewlineDoc();
  24. impl InitialDocumentText for NewlineDoc {
  25. fn initial_delta() -> RichTextDelta {
  26. initial_delta()
  27. }
  28. }
  29. pub struct ClientDocument {
  30. delta: RichTextDelta,
  31. history: History,
  32. view: ViewExtensions,
  33. last_edit_time: usize,
  34. notify: Option<mpsc::UnboundedSender<()>>,
  35. }
  36. impl ClientDocument {
  37. pub fn new<C: InitialDocumentText>() -> Self {
  38. Self::from_delta(C::initial_delta())
  39. }
  40. pub fn from_delta(delta: RichTextDelta) -> Self {
  41. ClientDocument {
  42. delta,
  43. history: History::new(),
  44. view: ViewExtensions::new(),
  45. last_edit_time: 0,
  46. notify: None,
  47. }
  48. }
  49. pub fn from_json(json: &str) -> Result<Self, CollaborateError> {
  50. let delta = RichTextDelta::from_json(json)?;
  51. Ok(Self::from_delta(delta))
  52. }
  53. pub fn to_json(&self) -> String {
  54. self.delta.to_json()
  55. }
  56. pub fn to_bytes(&self) -> Vec<u8> {
  57. self.delta.clone().to_bytes().to_vec()
  58. }
  59. pub fn to_plain_string(&self) -> String {
  60. self.delta.apply("").unwrap()
  61. }
  62. pub fn delta(&self) -> &RichTextDelta {
  63. &self.delta
  64. }
  65. pub fn md5(&self) -> String {
  66. let bytes = self.to_bytes();
  67. format!("{:x}", md5::compute(bytes))
  68. }
  69. pub fn set_notify(&mut self, notify: mpsc::UnboundedSender<()>) {
  70. self.notify = Some(notify);
  71. }
  72. pub fn set_delta(&mut self, data: RichTextDelta) {
  73. tracing::trace!("document: {}", data.to_json());
  74. self.delta = data;
  75. match &self.notify {
  76. None => {}
  77. Some(notify) => {
  78. let _ = notify.send(());
  79. }
  80. }
  81. }
  82. pub fn compose_delta(&mut self, delta: RichTextDelta) -> Result<(), CollaborateError> {
  83. tracing::trace!("{} compose {}", &self.delta.to_json(), delta.to_json());
  84. let composed_delta = self.delta.compose(&delta)?;
  85. let mut undo_delta = delta.invert(&self.delta);
  86. let now = chrono::Utc::now().timestamp_millis() as usize;
  87. if now - self.last_edit_time < RECORD_THRESHOLD {
  88. if let Some(last_delta) = self.history.undo() {
  89. tracing::trace!("compose previous change");
  90. tracing::trace!("current = {}", undo_delta);
  91. tracing::trace!("previous = {}", last_delta);
  92. undo_delta = undo_delta.compose(&last_delta)?;
  93. }
  94. } else {
  95. self.last_edit_time = now;
  96. }
  97. if !undo_delta.is_empty() {
  98. tracing::trace!("add history delta: {}", undo_delta);
  99. self.history.record(undo_delta);
  100. }
  101. self.set_delta(composed_delta);
  102. Ok(())
  103. }
  104. pub fn insert<T: ToString>(&mut self, index: usize, data: T) -> Result<RichTextDelta, CollaborateError> {
  105. let text = data.to_string();
  106. let interval = Interval::new(index, index);
  107. let _ = validate_interval(&self.delta, &interval)?;
  108. let delta = self.view.insert(&self.delta, &text, interval)?;
  109. self.compose_delta(delta.clone())?;
  110. Ok(delta)
  111. }
  112. pub fn delete(&mut self, interval: Interval) -> Result<RichTextDelta, CollaborateError> {
  113. let _ = validate_interval(&self.delta, &interval)?;
  114. debug_assert!(!interval.is_empty());
  115. let delete = self.view.delete(&self.delta, interval)?;
  116. if !delete.is_empty() {
  117. let _ = self.compose_delta(delete.clone())?;
  118. }
  119. Ok(delete)
  120. }
  121. pub fn format(
  122. &mut self,
  123. interval: Interval,
  124. attribute: RichTextAttribute,
  125. ) -> Result<RichTextDelta, CollaborateError> {
  126. let _ = validate_interval(&self.delta, &interval)?;
  127. tracing::trace!("format {} with {}", interval, attribute);
  128. let format_delta = self.view.format(&self.delta, attribute, interval).unwrap();
  129. self.compose_delta(format_delta.clone())?;
  130. Ok(format_delta)
  131. }
  132. pub fn replace<T: ToString>(&mut self, interval: Interval, data: T) -> Result<RichTextDelta, CollaborateError> {
  133. let _ = validate_interval(&self.delta, &interval)?;
  134. let mut delta = RichTextDelta::default();
  135. let text = data.to_string();
  136. if !text.is_empty() {
  137. delta = self.view.insert(&self.delta, &text, interval)?;
  138. self.compose_delta(delta.clone())?;
  139. }
  140. if !interval.is_empty() {
  141. let delete = self.delete(interval)?;
  142. delta = delta.compose(&delete)?;
  143. }
  144. Ok(delta)
  145. }
  146. pub fn can_undo(&self) -> bool {
  147. self.history.can_undo()
  148. }
  149. pub fn can_redo(&self) -> bool {
  150. self.history.can_redo()
  151. }
  152. pub fn undo(&mut self) -> Result<UndoResult, CollaborateError> {
  153. match self.history.undo() {
  154. None => Err(CollaborateError::undo().context("Undo stack is empty")),
  155. Some(undo_delta) => {
  156. let (new_delta, inverted_delta) = self.invert(&undo_delta)?;
  157. self.set_delta(new_delta);
  158. self.history.add_redo(inverted_delta);
  159. Ok(UndoResult { delta: undo_delta })
  160. }
  161. }
  162. }
  163. pub fn redo(&mut self) -> Result<UndoResult, CollaborateError> {
  164. match self.history.redo() {
  165. None => Err(CollaborateError::redo()),
  166. Some(redo_delta) => {
  167. let (new_delta, inverted_delta) = self.invert(&redo_delta)?;
  168. self.set_delta(new_delta);
  169. self.history.add_undo(inverted_delta);
  170. Ok(UndoResult { delta: redo_delta })
  171. }
  172. }
  173. }
  174. pub fn is_empty(&self) -> bool {
  175. // The document is empty if its text is equal to the initial text.
  176. self.delta == NewlineDoc::initial_delta()
  177. }
  178. }
  179. impl ClientDocument {
  180. fn invert(&self, delta: &RichTextDelta) -> Result<(RichTextDelta, RichTextDelta), CollaborateError> {
  181. // c = a.compose(b)
  182. // d = b.invert(a)
  183. // a = c.compose(d)
  184. let new_delta = self.delta.compose(delta)?;
  185. let inverted_delta = delta.invert(&self.delta);
  186. Ok((new_delta, inverted_delta))
  187. }
  188. }
  189. fn validate_interval(delta: &RichTextDelta, interval: &Interval) -> Result<(), CollaborateError> {
  190. if delta.utf16_target_len < interval.end {
  191. log::error!("{:?} out of bounds. should 0..{}", interval, delta.utf16_target_len);
  192. return Err(CollaborateError::out_of_bound());
  193. }
  194. Ok(())
  195. }