refactor: consolidate nvim crates, extract server library, and update workspace dependencies

This commit is contained in:
Riz Ashraf committed 2026-10-04 01:42:59 +01:00
1 parent a083719cf1
commit 533adfd41b
53 files changed
+5967 -1230

No files matched your search

+71 -50
View File
@@ -23,11 +23,11 @@ impl McpTool for WriteClipboardHandler {
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let tool_args: WriteClipboardTool = serde_json::from_value(args)
.map_err(|e| crate::error::AppError::Internal(format!("Invalid args: {}", e)))?;
tokio::task::spawn_blocking(move || {
let res = tokio::task::spawn_blocking(move || {
let mut msgs = Vec::new();
if let Ok(mut clipboard) = Clipboard::new()
@@ -77,7 +77,10 @@ impl McpTool for WriteClipboardHandler {
}
})
.await
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))?
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
state.record_activity("clipboard", &res, None);
Ok(res)
}
}
@@ -101,51 +104,51 @@ impl McpTool for ReadClipboardHandler {
tokio::task::spawn_blocking(move || -> crate::error::Result<serde_json::Value> {
let mut out = serde_json::Map::new();
if let Ok(mut clipboard) = arboard::Clipboard::new()
&& let Ok(text) = clipboard.get_text()
&& !text.trim().is_empty()
{
out.insert("text".into(), json!(text));
}
if let Ok(mut clipboard) = arboard::Clipboard::new()
&& let Ok(image_data) = clipboard.get_image()
&& let Some(img) = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
image_data.width as u32,
image_data.height as u32,
image_data.bytes.into_owned(),
)
{
let mut dynamic_img = image::DynamicImage::ImageRgba8(img);
let max_dim = 1024;
if dynamic_img.width() > max_dim || dynamic_img.height() > max_dim {
dynamic_img = dynamic_img.resize(max_dim, max_dim, FilterType::Lanczos3);
if let Ok(mut clipboard) = arboard::Clipboard::new() {
if let Ok(text) = clipboard.get_text() {
if !text.trim().is_empty() {
out.insert("text".into(), json!(text));
}
}
let rgb_img = dynamic_img.into_rgb8();
let cache_dir = dirs::home_dir()
.unwrap_or_default()
.join(".gemini/mcp_memory/clipboard");
let _ = std::fs::create_dir_all(&cache_dir);
if let Ok(image_data) = clipboard.get_image() {
if let Some(img) = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
image_data.width as u32,
image_data.height as u32,
image_data.bytes.into_owned(),
) {
let mut dynamic_img = image::DynamicImage::ImageRgba8(img);
let max_dim = 1024;
if dynamic_img.width() > max_dim || dynamic_img.height() > max_dim {
dynamic_img = dynamic_img.resize(max_dim, max_dim, FilterType::Lanczos3);
}
let rgb_img = dynamic_img.into_rgb8();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let file_path = cache_dir.join(format!("clipboard_{}.jpg", timestamp));
let cache_dir = dirs::home_dir()
.unwrap_or_default()
.join(".gemini/mcp_memory/clipboard");
let _ = std::fs::create_dir_all(&cache_dir);
if rgb_img
.save_with_format(&file_path, image::ImageFormat::Jpeg)
.is_ok()
{
let path_str = file_path.to_string_lossy().to_string();
out.insert("image_path".into(), json!(path_str));
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let file_path = cache_dir.join(format!("clipboard_{}.jpg", timestamp));
// Read image bytes for base64 encoding if needed by vision
if let Ok(bytes) = std::fs::read(&file_path) {
use base64::Engine;
let b64 = base64::engine::general_purpose::STANDARD.encode(&bytes);
out.insert("image_base64".into(), json!(b64));
if rgb_img
.save_with_format(&file_path, image::ImageFormat::Jpeg)
.is_ok()
{
let path_str = file_path.to_string_lossy().to_string();
out.insert("image_path".into(), json!(path_str));
// Read image bytes for base64 encoding if needed by vision
if let Ok(bytes) = std::fs::read(&file_path) {
use base64::Engine;
let b64 = base64::engine::general_purpose::STANDARD.encode(&bytes);
out.insert("image_base64".into(), json!(b64));
}
}
}
}
}
@@ -176,6 +179,7 @@ impl McpTool for ReadClipboardHandler {
}
}
state.record_activity("clipboard", "Read contents from OS clipboard", None);
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&final_obj)?)
}
}
@@ -203,14 +207,14 @@ impl McpTool for ToggleClipboardWatchModeHandler {
let mut watch_mode = state.clipboard_watch_mode.write().await;
*watch_mode = tool_args.enable;
if tool_args.enable {
Ok(
"Clipboard watch mode enabled. Changes will be ingested as StickyNotes."
.to_string(),
)
let status_msg = if tool_args.enable {
"Clipboard watch mode enabled. Changes will be ingested as StickyNotes."
} else {
Ok("Clipboard watch mode disabled.".to_string())
}
"Clipboard watch mode disabled."
};
state.record_activity("clipboard", status_msg, None);
Ok(status_msg.to_string())
}
}
@@ -280,4 +284,21 @@ mod tests {
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert!(parsed.is_object());
}
#[tokio::test]
async fn test_read_clipboard_empty() {
let dir = tempfile::tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let handler = ReadClipboardHandler;
let result = handler
.execute(serde_json::json!({}), state)
.await
.map_err(|e| format!("Failed to read clipboard: {}", e))
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert!(parsed.is_object());
}
}