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