use std::{
collections::HashMap,
path::PathBuf,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
use crossbeam_channel::{Receiver, Sender};
use lsp_types::{
CompletionResponse, LogMessageParams, ProgressParams, PublishDiagnosticsParams,
ShowMessageParams,
};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use crate::{
file::FileNodeItem,
plugin::{PluginId, VoltInfo, VoltMetadata},
source_control::DiffInfo,
terminal::TermId,
RequestId, RpcError, RpcMessage,
};
pub enum CoreRpc {
Request(RequestId, CoreRequest),
Notification(Box<CoreNotification>), Shutdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum CoreNotification {
ProxyConnected {},
OpenFileChanged {
path: PathBuf,
content: String,
},
CompletionResponse {
request_id: usize,
input: String,
resp: CompletionResponse,
plugin_id: PluginId,
},
ReloadBuffer {
path: PathBuf,
content: String,
rev: u64,
},
OpenPaths {
window_tab_id: Option<(usize, usize)>,
folders: Vec<PathBuf>,
files: Vec<PathBuf>,
},
WorkspaceFileChange {},
PublishDiagnostics {
diagnostics: PublishDiagnosticsParams,
},
WorkDoneProgress {
progress: ProgressParams,
},
ShowMessage {
title: String,
message: ShowMessageParams,
},
LogMessage {
message: LogMessageParams,
},
HomeDir {
path: PathBuf,
},
VoltInstalled {
volt: VoltMetadata,
only_installing: bool,
},
VoltInstalling {
volt: VoltMetadata,
error: String,
},
VoltRemoving {
volt: VoltMetadata,
error: String,
},
VoltRemoved {
volt: VoltInfo,
only_installing: bool,
},
ListDir {
items: Vec<FileNodeItem>,
},
DiffFiles {
files: Vec<PathBuf>,
},
DiffInfo {
diff: DiffInfo,
},
UpdateTerminal {
term_id: TermId,
content: String,
},
CloseTerminal {
term_id: TermId,
},
Log {
level: String,
message: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CoreRequest {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum CoreResponse {}
pub type CoreMessage = RpcMessage<CoreRequest, CoreNotification, CoreResponse>;
pub trait CoreHandler {
fn handle_notification(&mut self, rpc: CoreNotification);
fn handle_request(&mut self, id: RequestId, rpc: CoreRequest);
}
#[derive(Clone)]
pub struct CoreRpcHandler {
tx: Sender<CoreRpc>,
rx: Receiver<CoreRpc>,
id: Arc<AtomicU64>,
#[allow(clippy::type_complexity)]
pending: Arc<Mutex<HashMap<u64, Sender<Result<CoreResponse, RpcError>>>>>,
}
impl CoreRpcHandler {
pub fn new() -> Self {
let (tx, rx) = crossbeam_channel::unbounded();
Self {
tx,
rx,
id: Arc::new(AtomicU64::new(0)),
pending: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn mainloop<H>(&self, handler: &mut H)
where
H: CoreHandler,
{
for msg in &self.rx {
match msg {
CoreRpc::Request(id, rpc) => {
handler.handle_request(id, rpc);
}
CoreRpc::Notification(rpc) => {
handler.handle_notification(*rpc);
}
CoreRpc::Shutdown => {
return;
}
}
}
}
pub fn rx(&self) -> &Receiver<CoreRpc> {
&self.rx
}
pub fn handle_response(
&self,
id: RequestId,
response: Result<CoreResponse, RpcError>,
) {
let tx = { self.pending.lock().remove(&id) };
if let Some(tx) = tx {
let _ = tx.send(response);
}
}
pub fn request(&self, request: CoreRequest) -> Result<CoreResponse, RpcError> {
let (tx, rx) = crossbeam_channel::bounded(1);
let id = self.id.fetch_add(1, Ordering::Relaxed);
{
let mut pending = self.pending.lock();
pending.insert(id, tx);
}
let _ = self.tx.send(CoreRpc::Request(id, request));
rx.recv().unwrap_or_else(|_| {
Err(RpcError {
code: 0,
message: "io error".to_string(),
})
})
}
pub fn shutdown(&self) {
let _ = self.tx.send(CoreRpc::Shutdown);
}
pub fn notification(&self, notification: CoreNotification) {
let _ = self.tx.send(CoreRpc::Notification(Box::new(notification)));
}
pub fn proxy_connected(&self) {
self.notification(CoreNotification::ProxyConnected {});
}
pub fn workspace_file_change(&self) {
self.notification(CoreNotification::WorkspaceFileChange {});
}
pub fn diff_info(&self, diff: DiffInfo) {
self.notification(CoreNotification::DiffInfo { diff });
}
pub fn open_file_changed(&self, path: PathBuf, content: String) {
self.notification(CoreNotification::OpenFileChanged { path, content });
}
pub fn completion_response(
&self,
request_id: usize,
input: String,
resp: CompletionResponse,
plugin_id: PluginId,
) {
self.notification(CoreNotification::CompletionResponse {
request_id,
input,
resp,
plugin_id,
});
}
pub fn volt_installed(&self, volt: VoltMetadata, only_installing: bool) {
self.notification(CoreNotification::VoltInstalled {
volt,
only_installing,
});
}
pub fn volt_installing(&self, volt: VoltMetadata, error: String) {
self.notification(CoreNotification::VoltInstalling { volt, error });
}
pub fn volt_removing(&self, volt: VoltMetadata, error: String) {
self.notification(CoreNotification::VoltRemoving { volt, error });
}
pub fn volt_removed(&self, volt: VoltInfo, only_installing: bool) {
self.notification(CoreNotification::VoltRemoved {
volt,
only_installing,
});
}
pub fn log(&self, level: log::Level, message: String) {
self.notification(CoreNotification::Log {
level: level.as_str().to_string(),
message,
});
}
pub fn publish_diagnostics(&self, diagnostics: PublishDiagnosticsParams) {
self.notification(CoreNotification::PublishDiagnostics { diagnostics });
}
pub fn work_done_progress(&self, progress: ProgressParams) {
self.notification(CoreNotification::WorkDoneProgress { progress });
}
pub fn show_message(&self, title: String, message: ShowMessageParams) {
self.notification(CoreNotification::ShowMessage { title, message });
}
pub fn log_message(&self, message: LogMessageParams) {
self.notification(CoreNotification::LogMessage { message });
}
pub fn close_terminal(&self, term_id: TermId) {
self.notification(CoreNotification::CloseTerminal { term_id });
}
pub fn update_terminal(&self, term_id: TermId, content: String) {
self.notification(CoreNotification::UpdateTerminal { term_id, content });
}
}
impl Default for CoreRpcHandler {
fn default() -> Self {
Self::new()
}
}