Skip to content

Commit

Permalink
Allow passing Python timestamp function to undo manager (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart authored Jan 9, 2025
1 parent 70cc209 commit c9a029e
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 6 deletions.
2 changes: 1 addition & 1 deletion python/pycrdt/_pycrdt.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ class XmlText:
class UndoManager:
"""Undo manager."""

def __init__(self, doc: Doc, capture_timeout_millis: int = 500) -> None:
def __init__(self, doc: Doc, capture_timeout_millis, timestamp: Callable[[], int]) -> None:
"""Creates an undo manager."""

def expand_scope(self, scope: Text | Array | Map) -> None:
Expand Down
12 changes: 9 additions & 3 deletions python/pycrdt/_undo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from time import time_ns
from typing import TYPE_CHECKING, Any, Callable

from ._base import BaseType
from ._pycrdt import (
Expand All @@ -15,6 +16,10 @@
from ._doc import Doc


def timestamp() -> int:
return time_ns() // 1_000_000


class UndoManager:
"""
The undo manager allows to perform undo/redo operations on shared types.
Expand All @@ -31,13 +36,14 @@ def __init__(
doc: Doc | None = None,
scopes: list[BaseType] = [],
capture_timeout_millis: int = 500,
timestamp: Callable[[], int] = timestamp,
) -> None:
"""
Args:
doc: The document the undo manager will work with.
scopes: A list of shared types the undo manager will work with.
capture_timeout_millis: A time interval for grouping changes that will be undone/redone.
timestamp: A function that returns a timestamp as an integer number of milli-seconds.
Raises:
RuntimeError: UndoManager must be created with doc or scopes.
Expand All @@ -48,7 +54,7 @@ def __init__(
doc = scopes[0].doc
elif scopes:
raise RuntimeError("UndoManager must be created with doc or scopes")
self._undo_manager = _UndoManager(doc._doc, capture_timeout_millis)
self._undo_manager = _UndoManager(doc._doc, capture_timeout_millis, timestamp)
for scope in scopes:
self.expand_scope(scope)

Expand Down
23 changes: 21 additions & 2 deletions src/undo.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyList;
use pyo3::exceptions::PyRuntimeError;
Expand All @@ -8,11 +10,23 @@ use yrs::undo::{
Options,
StackItem as _StackItem,
};
use yrs::sync::{Clock, Timestamp};
use crate::doc::Doc;
use crate::text::Text;
use crate::array::Array;
use crate::map::Map;

struct PythonClock {
timestamp: PyObject,
}

impl Clock for PythonClock {
fn now(&self) -> Timestamp {
Python::with_gil(|py| {
self.timestamp.call0(py).expect("Error getting timestamp").extract(py).expect("Could not convert timestamp to int")
})
}
}

#[pyclass(unsendable)]
pub struct UndoManager {
Expand All @@ -22,8 +36,13 @@ pub struct UndoManager {
#[pymethods]
impl UndoManager {
#[new]
fn new(doc: &Doc, capture_timeout_millis: u64) -> Self {
let mut options = Options::default();
fn new(doc: &Doc, capture_timeout_millis: u64, timestamp: PyObject) -> Self {
let mut options = Options {
capture_timeout_millis: 500,
tracked_origins: HashSet::new(),
capture_transaction: None,
timestamp: Arc::new(PythonClock {timestamp}),
};
options.capture_timeout_millis = capture_timeout_millis;
let undo_manager = _UndoManager::with_options(&doc.doc, options);
UndoManager { undo_manager }
Expand Down

0 comments on commit c9a029e

Please sign in to comment.