use jsonrpc_core::futures::FutureExt;
use jsonrpc_core::{IoHandler, Params};
use lsp_types::{
InitializeParams, InitializeResult, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, Uri,
};
use serde_json::Value;
use std::path::PathBuf;
use tokio::sync::RwLock;
use anyhow::Result;
#[derive(Debug)]
pub struct IdeSync {
sessions: RwLock<Vec<IdeSession>>,
}
#[derive(Debug, Clone)]
struct IdeSession {
id: String,
workspace_root: PathBuf,
capabilities: ServerCapabilities,
}
impl IdeSync {
pub fn new() -> Self {
Self {
sessions: RwLock::new(Vec::new()),
}
}
pub async fn start_language_server(
&self,
workspace_path: PathBuf,
) -> Result<()> {
let mut handler = IoHandler::new();
handler.add_method("initialize", |params: Params| {
async move {
let params: InitializeParams = params.parse().unwrap();
let _root_path = if let Some(workspace_folders) = ¶ms.workspace_folders {
if let Some(first_folder) = workspace_folders.first() {
uri_to_file_path(&first_folder.uri)
} else {
None
}
} else {
if let Some(uri) = ¶ms.root_uri {
uri_to_file_path(uri)
} else {
None
}
};
let init_result = InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
)),
..Default::default()
},
server_info: None,
};
Ok(serde_json::to_value(init_result).unwrap())
}
.boxed()
});
handler.add_method("initialized", |_params: Params| {
async move { Ok(Value::Null) }.boxed()
});
handler.add_method("textDocument/didOpen", |params: Params| {
async move {
println!("Document opened: {:?}", params);
Ok(Value::Null)
}
.boxed()
});
handler.add_method("textDocument/didChange", |params: Params| {
async move {
println!("Document changed: {:?}", params);
Ok(Value::Null)
}
.boxed()
});
handler.add_method("textDocument/didSave", |params: Params| {
async move {
println!("Document saved: {:?}", params);
Ok(Value::Null)
}
.boxed()
});
handler.add_method("textDocument/didClose", |params: Params| {
async move {
println!("Document closed: {:?}", params);
Ok(Value::Null)
}
.boxed()
});
let session = IdeSession {
id: uuid::Uuid::new_v4().to_string(),
workspace_root: workspace_path,
capabilities: ServerCapabilities::default(),
};
self.sessions.write().await.push(session);
Ok(())
}
pub async fn send_did_change(
&self,
file_path: &str,
content: &str,
) -> Result<()> {
println!("Sending didChange for {}: {}", file_path, content);
Ok(())
}
pub async fn get_completions(
&self,
file_path: &str,
line: u32,
character: u32,
) -> Result<Vec<String>> {
println!(
"Requesting completions for {}:{}:{}",
file_path, line, character
);
Ok(vec!["example_completion".to_string()])
}
}
fn uri_to_file_path(uri: &Uri) -> Option<PathBuf> {
let uri_str = uri.as_str();
if uri_str.starts_with("file://") {
let path_str = &uri_str[7..];
let decoded_path = path_str.replace("%20", " ");
Some(PathBuf::from(decoded_path))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_ide_sync() {
let ide_sync = IdeSync::new();
let temp_dir = std::env::temp_dir();
assert!(ide_sync.start_language_server(temp_dir).await.is_ok());
}
}