diff --git a/book/src/usage.md b/book/src/usage.md index e014821931934..c22301bb6195d 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -67,6 +67,22 @@ are joined with newlines. Pasting from these registers will paste multiple selections if the clipboard was last yanked to by the Helix session. Otherwise the clipboard contents are pasted as one selection. +### Tmux clipboard issues + +Tmux clipboard support has bad interactions with some specific remote shell +and terminal combinations (e.g. kitty + mosh + tmux). Tmux allows for escaping +strings directly to the terminal, bypassing the remote shell and allowing work +arounds to this problem. Helix is able to work around this problem by escaping +the OSC52 sequences in tmux. To use this feature, you need to enable the tmux +option with the tmux command: + +``` +set-window-option -g allow-passthrough on +``` + +Helix will detect this and use the feature to bypass tmux and the remote +shell. You can find more information [here](https://sunaku.github.io/tmux-yank-osc52.html). + ## Surround Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround). diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 8dd7ebb168197..fa09ac2f46a69 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -96,6 +96,37 @@ pub fn get_clipboard_provider() -> Box { Box::new(provider::FallbackProvider::new()) } +// Check if we are in tmux and the allow-passthrough is on. +fn tmux_allow_passthrough() -> bool { + use helix_stdx::env::env_var_is_set; + + if !env_var_is_set("TMUX") { + return false; + } + + use std::process::Command; + let result = Command::new("tmux") + .arg("show") + .arg("-gw") + .arg("allow-passthrough") + .output(); + let output = match result { + Ok(out) => out, + Err(_) => return false, + }; + + if !output.status.success() { + return false; + } + + let output = match String::from_utf8(output.stdout) { + Ok(out) => out, + Err(_) => return false, + }; + + output.contains("on") +} + #[cfg(not(any(windows, target_os = "wasm32", target_os = "macos")))] pub fn get_clipboard_provider() -> Box { use helix_stdx::env::{binary_exists, env_var_is_set}; @@ -138,7 +169,7 @@ pub fn get_clipboard_provider() -> Box { paste => "termux-clipboard-get"; copy => "termux-clipboard-set"; } - } else if env_var_is_set("TMUX") && binary_exists("tmux") { + } else if env_var_is_set("TMUX") && binary_exists("tmux") && !tmux_allow_passthrough() { command_provider! { paste => "tmux", "save-buffer", "-"; copy => "tmux", "load-buffer", "-w", "-"; @@ -156,6 +187,7 @@ pub mod provider { #[cfg(feature = "term")] mod osc52 { + use crate::clipboard::tmux_allow_passthrough; use {super::ClipboardType, crate::base64}; #[derive(Debug)] @@ -179,8 +211,14 @@ pub mod provider { ClipboardType::Clipboard => "c", ClipboardType::Selection => "p", }; + // Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/ - write!(f, "\x1b]52;{};{}\x1b\\", kind, &self.encoded_content) + let mut osc52 = format!("\x1b]52;{};{}\x07", kind, &self.encoded_content); + if tmux_allow_passthrough() { + // If we are inside tmux and we are allow to passthrough, escape it too. + osc52 = format!("\x1bPtmux;\x1b{}\x1b\\", osc52); + } + f.write_str(&osc52) } } }