Skip to main content

strop_engine/editor/remote/
mod.rs

1//! Editor-side remote workspace ownership. Transport actors live in strop-remote;
2//! this module owns view intent, periodic reads, explicit connections and browsing.
3mod chooser;
4mod commands;
5mod controls;
6pub(super) mod follow;
7mod history;
8pub(crate) mod save;
9#[cfg(test)]
10mod tests;
11pub mod view;
12
13use super::document::DocumentSource;
14use super::io::Opened;
15use super::{Document, Editor};
16use std::collections::HashMap;
17use strop_core::id::{BufferRevision, DocumentId};
18use strop_core::worker::{Completion, Ticket, WorkerId};
19use strop_remote::{ConnectionLease, ReadLimit, RemoteClient};
20use strop_workspace::{RemoteEndpoint, RemoteFile};
21pub use view::RemoteView;
22
23pub(crate) struct RemoteState {
24    client: RemoteClient,
25    following: HashMap<DocumentId, FollowOwner>,
26    controls: HashMap<WorkerId, ControlKey>,
27    pins: HashMap<RemoteEndpoint, ConnectionLease>,
28    choices: Option<Ticket<super::picker::PickerId>>,
29    destination_write: Option<Ticket<RemoteFile>>,
30    destination_queue: Vec<RemoteFile>,
31    writes: save::WriteState,
32}
33impl Default for RemoteState {
34    fn default() -> Self {
35        Self {
36            client: RemoteClient::new(),
37            following: HashMap::new(),
38            controls: HashMap::new(),
39            pins: HashMap::new(),
40            choices: None,
41            destination_write: None,
42            destination_queue: Vec::new(),
43            writes: save::WriteState::default(),
44        }
45    }
46}
47struct FollowOwner {
48    ticket: Ticket<FollowKey>,
49    read: Option<WorkerId>,
50    limit: ReadLimit,
51}
52#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
53pub struct FollowKey {
54    pub document: DocumentId,
55}
56#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
57pub struct FollowReadKey {
58    pub document: DocumentId,
59    pub owner: WorkerId,
60    pub revision: BufferRevision,
61}
62#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
63pub enum FollowChange {
64    Appended,
65    Reset,
66    Shrank,
67}
68#[derive(serde::Serialize, serde::Deserialize)]
69pub enum FollowUpdate {
70    Unchanged,
71    Window {
72        opened: Box<Opened>,
73        change: FollowChange,
74    },
75}
76#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
77pub enum RemoteControl {
78    Connect(RemoteEndpoint),
79    Disconnect(RemoteEndpoint),
80    DisconnectAll,
81    Connections,
82}
83#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84pub struct ControlKey {
85    pub document: DocumentId,
86    pub focus: u64,
87    pub operation: RemoteControl,
88}
89#[derive(serde::Serialize, serde::Deserialize)]
90pub enum ControlResult {
91    Connected {
92        endpoint: RemoteEndpoint,
93        #[serde(skip)]
94        lease: Option<ConnectionLease>,
95    },
96    Disconnected,
97    Listing(String),
98}
99#[derive(serde::Serialize, serde::Deserialize)]
100pub enum RemoteEvent {
101    Tick(Ticket<FollowKey>),
102    Timer(Completion<FollowKey, ()>),
103    Read(Box<Completion<FollowReadKey, FollowUpdate>>),
104    Control(Completion<ControlKey, ControlResult>),
105    Choices(Completion<super::picker::PickerId, chooser::RemoteChoices>),
106    DestinationWritten(Completion<RemoteFile, ()>),
107    Write(Box<Completion<save::RemoteWriteKey, save::RemoteWriteResult>>),
108}
109
110impl Editor {
111    pub(crate) fn remote_client(&self) -> RemoteClient {
112        self.remote.client.clone()
113    }
114    pub fn remote_file(&self) -> Option<&RemoteFile> {
115        match &self.cur().source {
116            DocumentSource::Remote(source) => Some(&source.file),
117            _ => None,
118        }
119    }
120    pub fn remote_endpoint(&self) -> Option<&RemoteEndpoint> {
121        self.remote_file()
122            .map(RemoteFile::endpoint)
123            .or_else(|| {
124                self.directory()
125                    .and_then(|source| source.location.filesystem.endpoint())
126            })
127            .or_else(|| {
128                self.cur()
129                    .git_context()
130                    .and_then(strop_git::GitContext::endpoint)
131            })
132    }
133    pub(crate) fn remote_window_complete(&self) -> bool {
134        self.cur()
135            .remote_metadata()
136            .is_some_and(|source| source.window.is_complete())
137            && !self.remote_following(self.current())
138    }
139    pub fn remote_following(&self, document: DocumentId) -> bool {
140        self.remote.following.contains_key(&document)
141    }
142    pub(crate) fn remote_work_pending(&self) -> bool {
143        !self.remote.controls.is_empty()
144            || self.remote.choices.is_some()
145            || self.remote.destination_write.is_some()
146            || !self.remote.destination_queue.is_empty()
147            || self.remote.writes.pending()
148            || self
149                .remote
150                .following
151                .values()
152                .any(|owner| owner.read.is_some())
153    }
154    pub(crate) fn handle_remote_event(&mut self, event: RemoteEvent) {
155        match event {
156            RemoteEvent::Write(completion) => self.remote_write_done(*completion),
157            RemoteEvent::Tick(ticket) => self.remote_follow_tick(ticket),
158            RemoteEvent::Timer(completion) => self.remote_follow_timer_done(completion),
159            RemoteEvent::Read(completion) => self.remote_follow_read(*completion),
160            RemoteEvent::Control(completion) => self.remote_control_done(completion),
161            RemoteEvent::Choices(completion) => self.remote_choices_done(completion),
162            RemoteEvent::DestinationWritten(completion) => {
163                self.remote_destination_written(completion)
164            }
165        }
166    }
167    pub(crate) fn stop_remote_work(&mut self) {
168        let documents: Vec<_> = self.remote.following.keys().copied().collect();
169        for document in documents {
170            self.stop_remote_follow(document);
171        }
172        self.remote.pins.clear();
173    }
174}