summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Linford <tlinford@users.noreply.github.com>2022-02-02 15:22:34 +0100
committerGitHub <noreply@github.com>2022-02-02 15:22:34 +0100
commit18709acde974736492972b2892f69cea5f55716f (patch)
treef7990efbe51196f141a94c17b042c91389fb283e
parent2799eb91607a56b99a9417249e6735b11deebd51 (diff)
feat(copy): allow osc52 copy destination configuration (#1022)
add copy_cliboard option to allow configuring copy destination to primary selection instead of default clipboard
-rw-r--r--default-plugins/status-bar/src/main.rs96
-rw-r--r--default-plugins/status-bar/src/second_line.rs11
-rw-r--r--zellij-server/src/screen.rs7
-rw-r--r--zellij-server/src/tab/clipboard.rs50
-rw-r--r--zellij-server/src/tab/copy_command.rs27
-rw-r--r--zellij-server/src/tab/mod.rs60
-rw-r--r--zellij-server/src/tab/unit/tab_tests.rs3
-rw-r--r--zellij-server/src/unit/screen_tests.rs3
-rw-r--r--zellij-tile/src/data.rs9
-rw-r--r--zellij-utils/assets/config/default.yaml8
-rw-r--r--zellij-utils/src/input/options.rs24
-rw-r--r--zellij-utils/src/lib.rs1
12 files changed, 189 insertions, 110 deletions
diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs
index 1a26ca14f..27dc3262d 100644
--- a/default-plugins/status-bar/src/main.rs
+++ b/default-plugins/status-bar/src/main.rs
@@ -24,7 +24,7 @@ struct State {
tabs: Vec<TabInfo>,
tip_name: String,
mode_info: ModeInfo,
- diplay_text_copied_hint: bool,
+ text_copy_destination: Option<CopyDestination>,
display_system_clipboard_failure: bool,
}
@@ -156,14 +156,14 @@ impl ZellijPlugin for State {
Event::TabUpdate(tabs) => {
self.tabs = tabs;
}
- Event::CopyToClipboard => {
- self.diplay_text_copied_hint = true;
+ Event::CopyToClipboard(copy_destination) => {
+ self.text_copy_destination = Some(copy_destination);
}
Event::SystemClipboardFailure => {
self.display_system_clipboard_failure = true;
}
Event::InputReceived => {
- self.diplay_text_copied_hint = false;
+ self.text_copy_destination = None;
self.display_system_clipboard_failure = false;
}
_ => {}
@@ -186,64 +186,7 @@ impl ZellijPlugin for State {
);
let first_line = format!("{}{}", superkey, ctrl_keys);
-
- let mut second_line = LinePart::default();
- for t in &mut self.tabs {
- if t.active {
- match self.mode_info.mode {
- InputMode::Normal => {
- if t.is_fullscreen_active {
- second_line = if self.diplay_text_copied_hint {
- text_copied_hint(&self.mode_info.palette)
- } else if self.display_system_clipboard_failure {
- system_clipboard_error(&self.mode_info.palette)
- } else {
- fullscreen_panes_to_hide(&self.mode_info.palette, t.panes_to_hide)
- }
- } else {
- second_line = if self.diplay_text_copied_hint {
- text_copied_hint(&self.mode_info.palette)
- } else if self.display_system_clipboard_failure {
- system_clipboard_error(&self.mode_info.palette)
- } else {
- keybinds(&self.mode_info, &self.tip_name, cols)
- }
- }
- }
- InputMode::Locked => {
- if t.is_fullscreen_active {
- second_line = if self.diplay_text_copied_hint {
- text_copied_hint(&self.mode_info.palette)
- } else if self.display_system_clipboard_failure {
- system_clipboard_error(&self.mode_info.palette)
- } else {
- locked_fullscreen_panes_to_hide(
- &self.mode_info.palette,
- t.panes_to_hide,
- )
- }
- } else {
- second_line = if self.diplay_text_copied_hint {
- text_copied_hint(&self.mode_info.palette)
- } else if self.display_system_clipboard_failure {
- system_clipboard_error(&self.mode_info.palette)
- } else {
- keybinds(&self.mode_info, &self.tip_name, cols)
- }
- }
- }
- _ => {
- second_line = if self.diplay_text_copied_hint {
- text_copied_hint(&self.mode_info.palette)
- } else if self.display_system_clipboard_failure {
- system_clipboard_error(&self.mode_info.palette)
- } else {
- keybinds(&self.mode_info, &self.tip_name, cols)
- }
- }
- }
- }
- }
+ let second_line = self.second_line(cols);
// [48;5;238m is gray background, [0K is so that it fills the rest of the line
// [m is background reset, [0K is so that it clears the rest of the line
@@ -258,3 +201,32 @@ impl ZellijPlugin for State {
println!("\u{1b}[m{}\u{1b}[0K", second_line);
}
}
+
+impl State {
+ fn second_line(&self, cols: usize) -> LinePart {
+ let active_tab = self.tabs.iter().find(|t| t.active);
+
+ if let Some(copy_destination) = self.text_copy_destination {
+ text_copied_hint(&self.mode_info.palette, copy_destination)
+ } else if self.display_system_clipboard_failure {
+ system_clipboard_error(&self.mode_info.palette)
+ } else if let Some(active_tab) = active_tab {
+ if active_tab.is_fullscreen_active {
+ match self.mode_info.mode {
+ InputMode::Normal => {
+ fullscreen_panes_to_hide(&self.mode_info.palette, active_tab.panes_to_hide)
+ }
+ InputMode::Locked => locked_fullscreen_panes_to_hide(
+ &self.mode_info.palette,
+ active_tab.panes_to_hide,
+ ),
+ _ => keybinds(&self.mode_info, &self.tip_name, cols),
+ }
+ } else {
+ keybinds(&self.mode_info, &self.tip_name, cols)
+ }
+ } else {
+ LinePart::default()
+ }
+ }
+}
diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs
index 32a444255..13e847b99 100644
--- a/default-plugins/status-bar/src/second_line.rs
+++ b/default-plugins/status-bar/src/second_line.rs
@@ -229,12 +229,19 @@ pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart {
best_effort_shortcut_list(help, tip_body.short, max_width)
}
-pub fn text_copied_hint(palette: &Palette) -> LinePart {
- let hint = " Text copied to clipboard";
+pub fn text_copied_hint(palette: &Palette, copy_destination: CopyDestination) -> LinePart {
let green_color = match palette.green {
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
PaletteColor::EightBit(color) => Fixed(color),
};
+ let hint = match copy_destination {
+ CopyDestination::Command => "Text piped to external command",
+ #[cfg(not(target_os = "macos"))]
+ CopyDestination::Primary => "Text copied to primary selection",
+ #[cfg(target_os = "macos")] // primary selection does not exist on macos
+ CopyDestination::Primary => "Text copied to clipboard",
+ CopyDestination::System => "Text copied to clipboard",
+ };
LinePart {
part: Style::new().fg(green_color).bold().paint(hint).to_string(),
len: hint.len(),
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index c8f80eba4..6039c6f63 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -6,6 +6,7 @@ use std::os::unix::io::RawFd;
use std::rc::Rc;
use std::str;
+use zellij_utils::input::options::Clipboard;
use zellij_utils::pane_size::Size;
use zellij_utils::{input::layout::Layout, position::Position, zellij_tile};
@@ -194,10 +195,12 @@ pub(crate) struct Screen {
draw_pane_frames: bool,
session_is_mirrored: bool,
copy_command: Option<String>,
+ copy_clipboard: Clipboard,
}
impl Screen {
/// Creates and returns a new [`Screen`].
+ #[allow(clippy::too_many_arguments)]
pub fn new(
bus: Bus<ScreenInstruction>,
client_attributes: &ClientAttributes,
@@ -206,6 +209,7 @@ impl Screen {
draw_pane_frames: bool,
session_is_mirrored: bool,
copy_command: Option<String>,
+ copy_clipboard: Clipboard,
) -> Self {
Screen {
bus,
@@ -222,6 +226,7 @@ impl Screen {
draw_pane_frames,
session_is_mirrored,
copy_command,
+ copy_clipboard,
}
}
@@ -495,6 +500,7 @@ impl Screen {
self.session_is_mirrored,
client_id,
self.copy_command.clone(),
+ self.copy_clipboard.clone(),
);
tab.apply_layout(layout, new_pids, tab_index, client_id);
if self.session_is_mirrored {
@@ -700,6 +706,7 @@ pub(crate) fn screen_thread_main(
draw_pane_frames,
session_is_mirrored,
config_options.copy_command,
+ config_options.copy_clipboard.unwrap_or_default(),
);
loop {
let (event, mut err_ctx) = screen
diff --git a/zellij-server/src/tab/clipboard.rs b/zellij-server/src/tab/clipboard.rs
new file mode 100644
index 000000000..0529bb002
--- /dev/null
+++ b/zellij-server/src/tab/clipboard.rs
@@ -0,0 +1,50 @@
+use zellij_tile::prelude::CopyDestination;
+use zellij_utils::{anyhow::Result, input::options::Clipboard};
+
+use crate::ClientId;
+
+use super::{copy_command::CopyCommand, Output};
+
+pub(crate) enum ClipboardProvider {
+ Command(CopyCommand),
+ Osc52(Clipboard),
+}
+
+impl ClipboardProvider {
+ pub(crate) fn set_content(
+ &self,
+ content: &str,
+ output: &mut Output,
+ client_ids: impl Iterator<Item = ClientId>,
+ ) -> Result<()> {
+ match &self {
+ ClipboardProvider::Command(command) => {
+ command.set(content.to_string())?;
+ }
+ ClipboardProvider::Osc52(clipboard) => {
+ let dest = match clipboard {
+ #[cfg(not(target_os = "macos"))]
+ Clipboard::Primary => 'p',
+ #[cfg(target_os = "macos")] // primary selection does not exist on macos
+ Clipboard::Primary => 'c',
+ Clipboard::System => 'c',
+ };
+ output.push_str_to_multiple_clients(
+ &format!("\u{1b}]52;{};{}\u{1b}\\", dest, base64::encode(content)),
+ client_ids,
+ );
+ }
+ };
+ Ok(())
+ }
+
+ pub(crate) fn as_copy_destination(&self) -> CopyDestination {
+ match self {
+ ClipboardProvider::Command(_) => CopyDestination::Command,
+ ClipboardProvider::Osc52(clipboard) => match clipboard {
+ Clipboard::Primary => CopyDestination::Primary,
+ Clipboard::System => CopyDestination::System,
+ },
+ }
+ }
+}
diff --git a/zellij-server/src/tab/copy_command.rs b/zellij-server/src/tab/copy_command.rs
index 612409502..0b2d84cf4 100644
--- a/zellij-server/src/tab/copy_command.rs
+++ b/zellij-server/src/tab/copy_command.rs
@@ -1,6 +1,8 @@
use std::io::prelude::*;
use std::process::{Command, Stdio};
+use zellij_utils::anyhow::{Context, Result};
+
pub struct CopyCommand {
command: String,
args: Vec<String>,
@@ -15,25 +17,18 @@ impl CopyCommand {
args: command_with_args.collect(),
}
}
- pub fn set(&self, value: String) -> bool {
- let process = match Command::new(self.command.clone())
+ pub fn set(&self, value: String) -> Result<()> {
+ let process = Command::new(self.command.clone())
.args(self.args.clone())
.stdin(Stdio::piped())
.spawn()
- {
- Err(why) => {
- eprintln!("couldn't spawn {}: {}", self.command, why);
- return false;
- }
- Ok(process) => process,
- };
+ .with_context(|| format!("couldn't spawn {}", self.command))?;
+ process
+ .stdin
+ .context("could not get stdin")?
+ .write_all(value.as_bytes())
+ .with_context(|| format!("couldn't write to {} stdin", self.command))?;
- match process.stdin.unwrap().write_all(value.as_bytes()) {
- Err(why) => {
- eprintln!("couldn't write to {} stdin: {}", self.command, why);
- false
- }
- Ok(_) => true,
- }
+ Ok(())
}
}
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 66533aa3f..c91ac87a9 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -1,11 +1,13 @@
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
//! as well as how they should be resized
+mod clipboard;
mod copy_command;
mod pane_grid;
mod pane_resizer;
use copy_command::CopyCommand;
+use zellij_utils::input::options::Clipboard;
use zellij_utils::position::{Column, Line};
use zellij_utils::{position::Position, serde, zellij_tile};
@@ -41,6 +43,8 @@ use zellij_utils::{
pane_size::{Offset, PaneGeom, Size, Viewport},
};
+use self::clipboard::ClipboardProvider;
+
// FIXME: This should be replaced by `RESIZE_PERCENT` at some point
const MIN_TERMINAL_HEIGHT: usize = 5;
const MIN_TERMINAL_WIDTH: usize = 5;
@@ -121,7 +125,7 @@ pub(crate) struct Tab {
session_is_mirrored: bool,
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
selecting_with_mouse: bool,
- copy_command: Option<String>,
+ clipboard_provider: ClipboardProvider,
// TODO: used only to focus the pane when the layout is loaded
// it seems that optimization is possible using `active_panes`
focus_pane_id: Option<PaneId>,
@@ -306,6 +310,7 @@ impl Tab {
session_is_mirrored: bool,
client_id: ClientId,
copy_command: Option<String>,
+ copy_clipboard: Clipboard,
) -> Self {
let panes = BTreeMap::new();
@@ -318,6 +323,11 @@ impl Tab {
let mut connected_clients = HashSet::new();
connected_clients.insert(client_id);
+ let clipboard_provider = match copy_command {
+ Some(command) => ClipboardProvider::Command(CopyCommand::new(command)),
+ None => ClipboardProvider::Osc52(copy_clipboard),
+ };
+
Tab {
index,
position,
@@ -342,7 +352,7 @@ impl Tab {
connected_clients_in_app,
connected_clients,
selecting_with_mouse: false,
- copy_command,
+ clipboard_provider,
focus_pane_id: None,
}
}
@@ -1936,7 +1946,7 @@ impl Tab {
.send_to_plugin(PluginInstruction::Update(
None,
None,
- Event::CopyToClipboard,
+ Event::CopyToClipboard(self.clipboard_provider.as_copy_destination()),
))
.unwrap();
}
@@ -1944,35 +1954,27 @@ impl Tab {
fn write_selection_to_clipboard(&self, selection: &str) {
let mut output = Output::default();
- let mut system_clipboard_failure = false;
output.add_clients(&self.connected_clients);
- match self.copy_command.clone() {
- Some(copy_command) => {
- let system_clipboard = CopyCommand::new(copy_command);
- system_clipboard_failure = !system_clipboard.set(selection.to_owned());
- }
- None => {
- output.push_str_to_multiple_clients(
- &format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)),
- self.connected_clients.iter().copied(),
- );
- }
- }
+ let client_ids = self.connected_clients.iter().copied();
- // TODO: ideally we should be sending the Render instruction from the screen
- self.senders
- .send_to_server(ServerInstruction::Render(Some(output)))
- .unwrap();
- self.senders
- .send_to_plugin(PluginInstruction::Update(
- None,
- None,
- if system_clipboard_failure {
+ let clipboard_event =
+ match self
+ .clipboard_provider
+ .set_content(selection, &mut output, client_ids)
+ {
+ Ok(_) => {
+ self.senders
+ .send_to_server(ServerInstruction::Render(Some(output)))
+ .unwrap();
+ Event::CopyToClipboard(self.clipboard_provider.as_copy_destination())
+ }
+ Err(err) => {
+ log::error!("could not write selection to clipboard: {}", err);
Event::SystemClipboardFailure
- } else {
- Event::CopyToClipboard
- },
- ))
+ }
+ };
+ self.senders
+ .send_to_plugin(PluginInstruction::Update(None, None, clipboard_event))
.unwrap();
}
fn is_inside_viewport(&self, pane_id: &PaneId) -> bool {
diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs
index 5ac63f6b8..527894aa4 100644
--- a/zellij-server/src/tab/unit/tab_tests.rs
+++ b/zellij-server/src/tab/unit/tab_tests.rs
@@ -9,6 +9,7 @@ use crate::{
use std::convert::TryInto;
use std::path::PathBuf;
use zellij_utils::input::layout::LayoutTemplate;
+use zellij_utils::input::options::Clipboard;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::Size;
@@ -97,6 +98,7 @@ fn create_new_tab(size: Size) -> Tab {
connected_clients.insert(client_id);
let connected_clients = Rc::new(RefCell::new(connected_clients));
let copy_command = None;
+ let copy_clipboard = Clipboard::default();
let mut tab = Tab::new(
index,
position,
@@ -112,6 +114,7 @@ fn create_new_tab(size: Size) -> Tab {
session_is_mirrored,
client_id,
copy_command,
+ copy_clipboard,
);
tab.apply_layout(
LayoutTemplate::default().try_into().unwrap(),
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index 1fbc85fce..97a6f3516 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -10,6 +10,7 @@ use std::convert::TryInto;
use std::path::PathBuf;
use zellij_utils::input::command::TerminalAction;
use zellij_utils::input::layout::LayoutTemplate;
+use zellij_utils::input::options::Clipboard;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::Size;
@@ -92,6 +93,7 @@ fn create_new_screen(size: Size) -> Screen {
let draw_pane_frames = false;
let session_is_mirrored = true;
let copy_command = None;
+ let copy_clipboard = Clipboard::default();
Screen::new(
bus,
&client_attributes,
@@ -100,6 +102,7 @@ fn create_new_screen(size: Size) -> Screen {
draw_pane_frames,
session_is_mirrored,
copy_command,
+ copy_clipboard,
)
}
diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs
index dae7cd09e..9b9f67b28 100644
--- a/zellij-tile/src/data.rs
+++ b/zellij-tile/src/data.rs
@@ -76,7 +76,7 @@ pub enum Event {
Key(Key),
Mouse(Mouse),
Timer(f64),
- CopyToClipboard,
+ CopyToClipboard(CopyDestination),
SystemClipboardFailure,
InputReceived,
Visible(bool),
@@ -267,3 +267,10 @@ impl Default for PluginCapabilities {
PluginCapabilities { arrow_fonts: true }
}
}
+
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+pub enum CopyDestination {
+ Command,
+ Primary,
+ System,
+}
diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml
index 749f7f8f2..607e8ea4b 100644
--- a/zellij-utils/assets/config/default.yaml
+++ b/zellij-utils/assets/config/default.yaml
@@ -477,3 +477,11 @@ plugins:
#copy_command: "xclip -selection clipboard" # x11
#copy_command: "wl-copy" # wayland
#copy_command: "pbcopy" # osx
+
+# Choose the destination for copied text
+# Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.
+# Does not apply when using copy_command.
+# Options:
+# - system (default)
+# - primary
+# copy_clipboard: primary
diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs
index 41f241f69..34b4dc88a 100644
--- a/zellij-utils/src/input/options.rs
+++ b/zellij-utils/src/input/options.rs
@@ -79,6 +79,25 @@ pub struct Options {
#[clap(long)]
#[serde(default)]
pub copy_command: Option<String>,
+
+ /// OSC52 destination clipboard
+ #[clap(long, arg_enum, ignore_case = true, conflicts_with = "copy-command")]
+ #[serde(default)]
+ pub copy_clipboard: Option<Clipboard>,
+}
+
+#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, PartialEq)]
+pub enum Clipboard {
+ #[serde(alias = "system")]
+ System,
+ #[serde(alias = "primary")]
+ Primary,
+}
+
+impl Default for Clipboard {
+ fn default() -> Self {
+ Self::System
+ }
}
impl Options {
@@ -105,6 +124,7 @@ impl Options {
let on_force_close = other.on_force_close.or(self.on_force_close);
let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
+ let copy_clipboard = other.copy_clipboard.or_else(|| self.copy_clipboard.clone());
Options {
simplified_ui,
@@ -118,6 +138,7 @@ impl Options {
on_force_close,
scroll_buffer_size,
copy_command,
+ copy_clipboard,
}
}
@@ -148,6 +169,7 @@ impl Options {
let on_force_close = other.on_force_close.or(self.on_force_close);
let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
+ let copy_clipboard = other.copy_clipboard.or_else(|| self.copy_clipboard.clone());
Options {
simplified_ui,
@@ -161,6 +183,7 @@ impl Options {
on_force_close,
scroll_buffer_size,
copy_command,
+ copy_clipboard,
}
}
@@ -210,6 +233,7 @@ impl From<CliOptions> for Options {
on_force_close: opts.on_force_close,
scroll_buffer_size: opts.scroll_buffer_size,
copy_command: opts.copy_command,
+ copy_clipboard: opts.copy_clipboard,
}
}
}
diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs
index 7115c1b46..7eb9825ab 100644
--- a/zellij-utils/src/lib.rs
+++ b/zellij-utils/src/lib.rs
@@ -11,6 +11,7 @@ pub mod position;
pub mod setup;
pub mod shared;
+pub use anyhow;
pub use async_std;
pub use clap;
pub use interprocess;