lapce_rpc/
core.rs

1use std::{
2    collections::HashMap,
3    path::PathBuf,
4    sync::{
5        atomic::{AtomicU64, Ordering},
6        Arc,
7    },
8};
9
10use crossbeam_channel::{Receiver, Sender};
11use lsp_types::{
12    CompletionResponse, LogMessageParams, ProgressParams, PublishDiagnosticsParams,
13    ShowMessageParams,
14};
15use parking_lot::Mutex;
16use serde::{Deserialize, Serialize};
17
18use crate::{
19    file::FileNodeItem,
20    plugin::{PluginId, VoltInfo, VoltMetadata},
21    source_control::DiffInfo,
22    terminal::TermId,
23    RequestId, RpcError, RpcMessage,
24};
25
26pub enum CoreRpc {
27    Request(RequestId, CoreRequest),
28    Notification(Box<CoreNotification>), // Box it since clippy complains
29    Shutdown,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34#[serde(tag = "method", content = "params")]
35pub enum CoreNotification {
36    ProxyConnected {},
37    OpenFileChanged {
38        path: PathBuf,
39        content: String,
40    },
41    CompletionResponse {
42        request_id: usize,
43        input: String,
44        resp: CompletionResponse,
45        plugin_id: PluginId,
46    },
47    ReloadBuffer {
48        path: PathBuf,
49        content: String,
50        rev: u64,
51    },
52    OpenPaths {
53        window_tab_id: Option<(usize, usize)>,
54        folders: Vec<PathBuf>,
55        files: Vec<PathBuf>,
56    },
57    WorkspaceFileChange {},
58    PublishDiagnostics {
59        diagnostics: PublishDiagnosticsParams,
60    },
61    WorkDoneProgress {
62        progress: ProgressParams,
63    },
64    ShowMessage {
65        title: String,
66        message: ShowMessageParams,
67    },
68    LogMessage {
69        message: LogMessageParams,
70    },
71    HomeDir {
72        path: PathBuf,
73    },
74    VoltInstalled {
75        volt: VoltMetadata,
76        only_installing: bool,
77    },
78    VoltInstalling {
79        volt: VoltMetadata,
80        error: String,
81    },
82    VoltRemoving {
83        volt: VoltMetadata,
84        error: String,
85    },
86    VoltRemoved {
87        volt: VoltInfo,
88        only_installing: bool,
89    },
90    ListDir {
91        items: Vec<FileNodeItem>,
92    },
93    DiffFiles {
94        files: Vec<PathBuf>,
95    },
96    DiffInfo {
97        diff: DiffInfo,
98    },
99    UpdateTerminal {
100        term_id: TermId,
101        content: String,
102    },
103    CloseTerminal {
104        term_id: TermId,
105    },
106    Log {
107        level: String,
108        message: String,
109    },
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub enum CoreRequest {}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case")]
117#[serde(tag = "method", content = "params")]
118pub enum CoreResponse {}
119
120pub type CoreMessage = RpcMessage<CoreRequest, CoreNotification, CoreResponse>;
121
122pub trait CoreHandler {
123    fn handle_notification(&mut self, rpc: CoreNotification);
124    fn handle_request(&mut self, id: RequestId, rpc: CoreRequest);
125}
126
127#[derive(Clone)]
128pub struct CoreRpcHandler {
129    tx: Sender<CoreRpc>,
130    rx: Receiver<CoreRpc>,
131    id: Arc<AtomicU64>,
132    #[allow(clippy::type_complexity)]
133    pending: Arc<Mutex<HashMap<u64, Sender<Result<CoreResponse, RpcError>>>>>,
134}
135
136impl CoreRpcHandler {
137    pub fn new() -> Self {
138        let (tx, rx) = crossbeam_channel::unbounded();
139        Self {
140            tx,
141            rx,
142            id: Arc::new(AtomicU64::new(0)),
143            pending: Arc::new(Mutex::new(HashMap::new())),
144        }
145    }
146
147    pub fn mainloop<H>(&self, handler: &mut H)
148    where
149        H: CoreHandler,
150    {
151        for msg in &self.rx {
152            match msg {
153                CoreRpc::Request(id, rpc) => {
154                    handler.handle_request(id, rpc);
155                }
156                CoreRpc::Notification(rpc) => {
157                    handler.handle_notification(*rpc);
158                }
159                CoreRpc::Shutdown => {
160                    return;
161                }
162            }
163        }
164    }
165
166    pub fn rx(&self) -> &Receiver<CoreRpc> {
167        &self.rx
168    }
169
170    pub fn handle_response(
171        &self,
172        id: RequestId,
173        response: Result<CoreResponse, RpcError>,
174    ) {
175        let tx = { self.pending.lock().remove(&id) };
176        if let Some(tx) = tx {
177            let _ = tx.send(response);
178        }
179    }
180
181    pub fn request(&self, request: CoreRequest) -> Result<CoreResponse, RpcError> {
182        let (tx, rx) = crossbeam_channel::bounded(1);
183        let id = self.id.fetch_add(1, Ordering::Relaxed);
184        {
185            let mut pending = self.pending.lock();
186            pending.insert(id, tx);
187        }
188        let _ = self.tx.send(CoreRpc::Request(id, request));
189        rx.recv().unwrap_or_else(|_| {
190            Err(RpcError {
191                code: 0,
192                message: "io error".to_string(),
193            })
194        })
195    }
196
197    pub fn shutdown(&self) {
198        let _ = self.tx.send(CoreRpc::Shutdown);
199    }
200
201    pub fn notification(&self, notification: CoreNotification) {
202        let _ = self.tx.send(CoreRpc::Notification(Box::new(notification)));
203    }
204
205    pub fn proxy_connected(&self) {
206        self.notification(CoreNotification::ProxyConnected {});
207    }
208
209    pub fn workspace_file_change(&self) {
210        self.notification(CoreNotification::WorkspaceFileChange {});
211    }
212
213    pub fn diff_info(&self, diff: DiffInfo) {
214        self.notification(CoreNotification::DiffInfo { diff });
215    }
216
217    pub fn open_file_changed(&self, path: PathBuf, content: String) {
218        self.notification(CoreNotification::OpenFileChanged { path, content });
219    }
220
221    pub fn completion_response(
222        &self,
223        request_id: usize,
224        input: String,
225        resp: CompletionResponse,
226        plugin_id: PluginId,
227    ) {
228        self.notification(CoreNotification::CompletionResponse {
229            request_id,
230            input,
231            resp,
232            plugin_id,
233        });
234    }
235
236    pub fn volt_installed(&self, volt: VoltMetadata, only_installing: bool) {
237        self.notification(CoreNotification::VoltInstalled {
238            volt,
239            only_installing,
240        });
241    }
242
243    pub fn volt_installing(&self, volt: VoltMetadata, error: String) {
244        self.notification(CoreNotification::VoltInstalling { volt, error });
245    }
246
247    pub fn volt_removing(&self, volt: VoltMetadata, error: String) {
248        self.notification(CoreNotification::VoltRemoving { volt, error });
249    }
250
251    pub fn volt_removed(&self, volt: VoltInfo, only_installing: bool) {
252        self.notification(CoreNotification::VoltRemoved {
253            volt,
254            only_installing,
255        });
256    }
257
258    pub fn log(&self, level: log::Level, message: String) {
259        self.notification(CoreNotification::Log {
260            level: level.as_str().to_string(),
261            message,
262        });
263    }
264
265    pub fn publish_diagnostics(&self, diagnostics: PublishDiagnosticsParams) {
266        self.notification(CoreNotification::PublishDiagnostics { diagnostics });
267    }
268
269    pub fn work_done_progress(&self, progress: ProgressParams) {
270        self.notification(CoreNotification::WorkDoneProgress { progress });
271    }
272
273    pub fn show_message(&self, title: String, message: ShowMessageParams) {
274        self.notification(CoreNotification::ShowMessage { title, message });
275    }
276
277    pub fn log_message(&self, message: LogMessageParams) {
278        self.notification(CoreNotification::LogMessage { message });
279    }
280
281    pub fn close_terminal(&self, term_id: TermId) {
282        self.notification(CoreNotification::CloseTerminal { term_id });
283    }
284
285    pub fn update_terminal(&self, term_id: TermId, content: String) {
286        self.notification(CoreNotification::UpdateTerminal { term_id, content });
287    }
288}
289
290impl Default for CoreRpcHandler {
291    fn default() -> Self {
292        Self::new()
293    }
294}