codemp/cursor/
controller.rs

1//! ### Cursor Controller
2//! A [Controller] implementation for [crate::api::Cursor] actions in a [crate::Workspace]
3
4use std::sync::Arc;
5
6use tokio::sync::{mpsc, oneshot, watch};
7
8use crate::{
9	api::{
10		controller::{AsyncReceiver, AsyncSender, ControllerCallback},
11		Controller, Cursor, Selection,
12	},
13	errors::ControllerResult,
14};
15use codemp_proto::{
16	cursor::{CursorPosition, RowCol},
17	files::BufferNode,
18};
19
20/// A [Controller] for asynchronously sending and receiving [Cursor] event.
21///
22/// An unique [CursorController] exists for each active [crate::Workspace].
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "py", pyo3::pyclass)]
25#[cfg_attr(feature = "js", napi_derive::napi)]
26pub struct CursorController(pub(crate) Arc<CursorControllerInner>);
27
28impl CursorController {
29	pub fn workspace_id(&self) -> &str {
30		&self.0.workspace_id
31	}
32}
33
34#[derive(Debug)]
35pub(crate) struct CursorControllerInner {
36	pub(crate) op: mpsc::UnboundedSender<CursorPosition>,
37	pub(crate) stream: mpsc::Sender<oneshot::Sender<Option<Cursor>>>,
38	pub(crate) poll: mpsc::UnboundedSender<oneshot::Sender<()>>,
39	pub(crate) callback: watch::Sender<Option<ControllerCallback<CursorController>>>,
40	pub(crate) workspace_id: String,
41}
42
43#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
44impl Controller<Selection, Cursor> for CursorController {}
45
46#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
47impl AsyncSender<Selection> for CursorController {
48	fn send(&self, mut cursor: Selection) -> ControllerResult<()> {
49		if cursor.start_row > cursor.end_row
50			|| (cursor.start_row == cursor.end_row && cursor.start_col > cursor.end_col)
51		{
52			std::mem::swap(&mut cursor.start_row, &mut cursor.end_row);
53			std::mem::swap(&mut cursor.start_col, &mut cursor.end_col);
54		}
55
56		Ok(self.0.op.send(CursorPosition {
57			buffer: BufferNode {
58				path: cursor.buffer,
59			},
60			start: RowCol {
61				row: cursor.start_row,
62				col: cursor.start_col,
63			},
64			end: RowCol {
65				row: cursor.end_row,
66				col: cursor.end_col,
67			},
68		})?)
69	}
70}
71
72#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
73impl AsyncReceiver<Cursor> for CursorController {
74	async fn try_recv(&self) -> ControllerResult<Option<Cursor>> {
75		let (tx, rx) = oneshot::channel();
76		self.0.stream.send(tx).await?;
77		Ok(rx.await?)
78	}
79
80	async fn poll(&self) -> ControllerResult<()> {
81		let (tx, rx) = oneshot::channel();
82		self.0.poll.send(tx)?;
83		rx.await?;
84		Ok(())
85	}
86
87	fn callback(&self, cb: impl Into<ControllerCallback<CursorController>>) {
88		if self.0.callback.send(Some(cb.into())).is_err() {
89			// TODO should we panic? we failed what we were supposed to do
90			tracing::error!("no active cursor worker to run registered callback!");
91		}
92	}
93
94	fn clear_callback(&self) {
95		if self.0.callback.send(None).is_err() {
96			tracing::warn!("no active cursor worker to clear callback");
97		}
98	}
99}