88from pycrdt import YMessageType , YSyncMessageType as YSyncMessageSubtype
99from jupyter_server_documents .ydocs import ydocs as jupyter_ydoc_classes
1010from jupyter_ydoc .ybasedoc import YBaseDoc
11+ from jupyter_events import EventLogger
1112from tornado .websocket import WebSocketHandler
1213from .yroom_file_api import YRoomFileAPI
14+ from .yroom_events_api import YRoomEventsAPI
1315
1416if TYPE_CHECKING :
1517 from typing import Literal , Tuple , Any
@@ -32,7 +34,7 @@ class YRoom:
3234
3335 file_api : YRoomFileAPI | None
3436 """
35- The `YRoomFileAPI` instance for this room. This is set to `None` if & only
37+ The `YRoomFileAPI` instance for this room. This is set to `None` only
3638 if `self.room_id == "JupyterLab:globalAwareness"`.
3739
3840 The file API provides `load_ydoc_content()` for loading the YDoc content
@@ -41,8 +43,16 @@ class YRoom:
4143 out-of-band changes.
4244 """
4345
46+ events_api : YRoomEventsAPI | None
47+ """
48+ A `YRoomEventsAPI` instance for this room that provides methods for emitting
49+ events through the `jupyter_events.EventLogger` singleton. This is set to
50+ `None` only if `self.room_id == "JupyterLab:globalAwareness"`.
51+ """
52+
4453 _jupyter_ydoc : YBaseDoc | None
4554 """JupyterYDoc"""
55+
4656 _ydoc : pycrdt .Doc
4757 """Ydoc"""
4858 _awareness : pycrdt .Awareness
@@ -78,6 +88,7 @@ def __init__(
7888 loop : asyncio .AbstractEventLoop ,
7989 fileid_manager : BaseFileIdManager ,
8090 contents_manager : AsyncContentsManager | ContentsManager ,
91+ event_logger : EventLogger
8192 ):
8293 # Bind instance attributes
8394 self .room_id = room_id
@@ -91,14 +102,18 @@ def __init__(
91102 self ._ydoc = pycrdt .Doc ()
92103 self ._awareness = pycrdt .Awareness (ydoc = self ._ydoc )
93104
94- # If this room is providing global awareness, set `file_api` and
95- # `_jupyter_ydoc` to `None` as the YDoc is unused .
105+ # If this room is providing global awareness, set unused optional
106+ # attributes to `None`.
96107 if self .room_id == "JupyterLab:globalAwareness" :
97108 self .file_api = None
98109 self ._jupyter_ydoc = None
110+ self .events_api = None
99111 else :
100- # Otherwise, initialize `_jupyter_ydoc` and `file_api`.
112+ # Otherwise, initialize optional attributes for document rooms
113+ # Initialize JupyterYDoc
101114 self ._jupyter_ydoc = self ._init_jupyter_ydoc ()
115+
116+ # Initialize YRoomFileAPI, start loading content
102117 self .file_api = YRoomFileAPI (
103118 room_id = self .room_id ,
104119 jupyter_ydoc = self ._jupyter_ydoc ,
@@ -108,12 +123,18 @@ def __init__(
108123 contents_manager = self ._contents_manager ,
109124 on_outofband_change = self .reload_ydoc
110125 )
111-
112- # Load the YDoc content after initializing
113126 self .file_api .load_ydoc_content ()
114127
115128 # Attach Jupyter YDoc observer to automatically save on change
116129 self ._jupyter_ydoc .observe (self ._on_jupyter_ydoc_update )
130+
131+ # Initialize YRoomEventsAPI
132+ self .events_api = YRoomEventsAPI (
133+ event_logger = event_logger ,
134+ fileid_manager = fileid_manager ,
135+ room_id = self .room_id ,
136+ log = self .log ,
137+ )
117138
118139 # Start observers on `self.ydoc` and `self.awareness` to ensure new
119140 # updates are always broadcast to all clients.
@@ -131,6 +152,18 @@ def __init__(
131152
132153 # Log notification that room is ready
133154 self .log .info (f"Room '{ self .room_id } ' initialized." )
155+
156+ # Emit events if defined
157+ if self .events_api :
158+ # Emit 'initialize' event
159+ self .events_api .emit_room_event ("initialize" )
160+
161+ # Emit 'load' event once content is loaded
162+ assert self .file_api
163+ async def emit_load_event ():
164+ await self .file_api .ydoc_content_loaded .wait ()
165+ self .events_api .emit_room_event ("load" )
166+ self ._loop .create_task (emit_load_event ())
134167
135168
136169 def _init_jupyter_ydoc (self ) -> YBaseDoc :
@@ -154,7 +187,7 @@ def _init_jupyter_ydoc(self) -> YBaseDoc:
154187 jupyter_ydoc_classes .get (file_type , jupyter_ydoc_classes ["file" ])
155188 )
156189
157- # Initialize Jupyter YDoc, add an observer to save it on change, return
190+ # Initialize Jupyter YDoc and return it
158191 jupyter_ydoc = JupyterYDocClass (ydoc = self ._ydoc , awareness = self ._awareness )
159192 return jupyter_ydoc
160193
@@ -602,6 +635,10 @@ def reload_ydoc(self) -> None:
602635 )
603636 self ._jupyter_ydoc .observe (self ._on_jupyter_ydoc_update )
604637
638+ # Emit 'overwrite' event as the YDoc content has been overwritten
639+ if self .events_api :
640+ self .events_api .emit_room_event ("overwrite" )
641+
605642
606643 async def stop (self ) -> None :
607644 """
0 commit comments