Remote copy/paste (#1003)

* add clipboard-rs

* set g:clipboard when starting remote session

* add rpcrequest handler

* add paste to clipboard rpcrequest

* seperate clipboard to bridge/clipboard

* update remote clipboard logic

- when paste: use line ending matching file format
- when copy: use line ending of remote system

* update remote clipboard setup

- use `neovide_no_custom_clipboard` to disable custom clipboard
- disable cache to not use old clipboard contents when error returned
  from `get_remote_clipboard`

* code cleanup and format
macos-click-through
Nguyễn Anh Khoa 3 years ago committed by GitHub
parent f0a9e1c20d
commit e6670a5413
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

62
Cargo.lock generated

@ -260,6 +260,28 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "clipboard"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
dependencies = [
"clipboard-win",
"objc",
"objc-foundation",
"objc_id",
"x11-clipboard",
]
[[package]]
name = "clipboard-win"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.0.3" version = "0.0.3"
@ -1317,6 +1339,7 @@ dependencies = [
"async-trait", "async-trait",
"cfg-if 0.1.10", "cfg-if 0.1.10",
"clap", "clap",
"clipboard",
"derive-new", "derive-new",
"dirs 2.0.2", "dirs 2.0.2",
"euclid", "euclid",
@ -1503,6 +1526,26 @@ dependencies = [
"malloc_buf", "malloc_buf",
] ]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.9.0" version = "1.9.0"
@ -2638,6 +2681,15 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "x11-clipboard"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea"
dependencies = [
"xcb",
]
[[package]] [[package]]
name = "x11-dl" name = "x11-dl"
version = "2.19.1" version = "2.19.1"
@ -2658,6 +2710,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "xcb"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
dependencies = [
"libc",
"log",
]
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.4" version = "0.3.4"

@ -45,6 +45,7 @@ gl = "0.14.0"
swash = "0.1.4" swash = "0.1.4"
clap="2.33.3" clap="2.33.3"
xdg="2.4.0" xdg="2.4.0"
clipboard="0.5.0"
[dev-dependencies] [dev-dependencies]
mockall = "0.7.0" mockall = "0.7.0"

@ -0,0 +1,56 @@
use std::error::Error;
use rmpv::Value;
use clipboard::ClipboardContext;
use clipboard::ClipboardProvider;
pub fn get_remote_clipboard(format: Option<&str>) -> Result<Value, Box<dyn Error>> {
let mut ctx: ClipboardContext = ClipboardProvider::new()?;
let clipboard_raw = ctx.get_contents()?.replace("\r", "");
let lines = if let Some("dos") = format {
// add \r to lines of current file format is dos
clipboard_raw.replace("\n", "\r\n")
} else {
// else, \r is stripped, leaving only \n
clipboard_raw
}
.split("\n")
.map(|line| Value::from(line))
.collect::<Vec<Value>>();
let lines = Value::from(lines);
// v paste is normal paste (everything in lines is pasted)
// V paste is paste with extra endline (line paste)
// If you want V paste, copy text with extra endline
let paste_mode = Value::from("v");
// returns [content: [String], paste_mode: v or V]
Ok(Value::from(vec![lines, paste_mode]))
}
pub fn set_remote_clipboard(arguments: Vec<Value>) -> Result<(), Box<dyn Error>> {
if arguments.len() != 3 {
return Err("expected exactly 3 arguments to set_remote_clipboard".into());
}
#[cfg(not(windows))]
let endline = "\n";
#[cfg(windows)]
let endline = "\r\n";
let lines = arguments[0]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|x| x.as_str().map(String::from))
.map(|s| s.replace("\r", "")) // strip \r
.collect::<Vec<String>>()
.join(endline)
})
.ok_or("can't build string from provided text")?;
let mut ctx: ClipboardContext = ClipboardProvider::new()?;
ctx.set_contents(lines)
}

@ -3,6 +3,7 @@ use log::trace;
use nvim_rs::{Handler, Neovim}; use nvim_rs::{Handler, Neovim};
use rmpv::Value; use rmpv::Value;
use crate::bridge::clipboard::{get_remote_clipboard, set_remote_clipboard};
#[cfg(windows)] #[cfg(windows)]
use crate::bridge::ui_commands::{ParallelCommand, UiCommand}; use crate::bridge::ui_commands::{ParallelCommand, UiCommand};
use crate::{ use crate::{
@ -27,6 +28,33 @@ impl NeovimHandler {
impl Handler for NeovimHandler { impl Handler for NeovimHandler {
type Writer = TxWrapper; type Writer = TxWrapper;
async fn handle_request(
&self,
event_name: String,
_arguments: Vec<Value>,
neovim: Neovim<TxWrapper>,
) -> Result<Value, Value> {
trace!("Neovim request: {:?}", &event_name);
match event_name.as_ref() {
"neovide.get_clipboard" => {
let endline_type = neovim
.command_output("set ff")
.await
.ok()
.and_then(|format| {
let mut s = format.split('=');
s.next();
s.next().map(String::from)
});
get_remote_clipboard(endline_type.as_deref())
.or(Err(Value::from("cannot get remote clipboard content")))
}
_ => Ok(Value::from("rpcrequest not handled")),
}
}
async fn handle_notify( async fn handle_notify(
&self, &self,
event_name: String, event_name: String,
@ -63,6 +91,9 @@ impl Handler for NeovimHandler {
"neovide.unregister_right_click" => { "neovide.unregister_right_click" => {
EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::UnregisterRightClick)); EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::UnregisterRightClick));
} }
"neovide.set_clipboard" => {
set_remote_clipboard(arguments).ok();
}
_ => {} _ => {}
} }
} }

@ -1,3 +1,4 @@
mod clipboard;
pub mod create; pub mod create;
mod events; mod events;
mod handler; mod handler;
@ -59,7 +60,11 @@ async fn start_neovim_runtime() {
} }
} }
setup_neovide_specific_state(&nvim).await; if let ConnectionMode::RemoteTcp(_) = connection_mode() {
setup_neovide_specific_state(&nvim, true).await;
} else {
setup_neovide_specific_state(&nvim, false).await;
}
let settings = SETTINGS.get::<CmdLineSettings>(); let settings = SETTINGS.get::<CmdLineSettings>();
let geometry = settings.geometry; let geometry = settings.geometry;

@ -7,7 +7,47 @@ use crate::{
error_handling::ResultPanicExplanation, error_handling::ResultPanicExplanation,
}; };
pub async fn setup_neovide_specific_state(nvim: &Neovim<TxWrapper>) { pub async fn setup_neovide_remote_clipboard(nvim: &Neovim<TxWrapper>, neovide_channel: u64) {
// users can opt-out with
// vim: `let g:neovide_no_custom_clipboard = v:true`
// lua: `vim.g.neovide_no_custom_clipboard = true`
let no_custom_clipboard = nvim
.get_var("neovide_no_custom_clipboard")
.await
.ok()
.and_then(|v| v.as_bool());
if Some(true) == no_custom_clipboard {
info!("Neovide working remotely but custom clipboard is disabled");
return;
}
// don't know how to setup lambdas with Value, so use string as command
let custom_clipboard = r#"
let g:clipboard = {
'name': 'custom',
'copy': {
'+': {
lines,
regtype -> rpcnotify(neovide_channel, 'neovide.set_clipboard', lines, regtype, '+')
},
'*': {
lines,
regtype -> rpcnotify(neovide_channel, 'neovide.set_clipboard', lines, regtype, '*')
},
},
'paste': {
'+': {-> rpcrequest(neovide_channel, 'neovide.get_clipboard', '+')},
'*': {-> rpcrequest(neovide_channel, 'neovide.get_clipboard', '*')},
},
'cache_enabled': 0
}
"#
.replace("\n", "") // make one-liner, because multiline is not accepted (?)
.replace("neovide_channel", &neovide_channel.to_string());
nvim.command(&custom_clipboard).await.ok();
}
pub async fn setup_neovide_specific_state(nvim: &Neovim<TxWrapper>, is_remote: bool) {
// Set variable indicating to user config that neovide is being used // Set variable indicating to user config that neovide is being used
nvim.set_var("neovide", Value::Boolean(true)) nvim.set_var("neovide", Value::Boolean(true))
.await .await
@ -94,6 +134,10 @@ pub async fn setup_neovide_specific_state(nvim: &Neovim<TxWrapper>) {
nvim.command("autocmd VimLeave * call rpcnotify(1, 'neovide.quit', v:exiting)") nvim.command("autocmd VimLeave * call rpcnotify(1, 'neovide.quit', v:exiting)")
.await .await
.ok(); .ok();
if is_remote {
setup_neovide_remote_clipboard(nvim, neovide_channel).await;
}
} }
#[cfg(windows)] #[cfg(windows)]

Loading…
Cancel
Save