Add a copy command option (#996)
Usage: zellij options --copy-command "xclip -sel clip" Co-authored-by: Christophe Verbinnen <christophev@knowbe4.com>
This commit is contained in:
parent
6af419528f
commit
9cc2645db0
9 changed files with 112 additions and 6 deletions
|
|
@ -10,7 +10,8 @@ use zellij_tile_utils::style;
|
||||||
|
|
||||||
use first_line::{ctrl_keys, superkey};
|
use first_line::{ctrl_keys, superkey};
|
||||||
use second_line::{
|
use second_line::{
|
||||||
fullscreen_panes_to_hide, keybinds, locked_fullscreen_panes_to_hide, text_copied_hint,
|
fullscreen_panes_to_hide, keybinds, locked_fullscreen_panes_to_hide, system_clipboard_error,
|
||||||
|
text_copied_hint,
|
||||||
};
|
};
|
||||||
use tip::utils::get_cached_tip_name;
|
use tip::utils::get_cached_tip_name;
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ struct State {
|
||||||
tip_name: String,
|
tip_name: String,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
diplay_text_copied_hint: bool,
|
diplay_text_copied_hint: bool,
|
||||||
|
display_system_clipboard_failure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
register_plugin!(State);
|
register_plugin!(State);
|
||||||
|
|
@ -142,6 +144,7 @@ impl ZellijPlugin for State {
|
||||||
EventType::TabUpdate,
|
EventType::TabUpdate,
|
||||||
EventType::CopyToClipboard,
|
EventType::CopyToClipboard,
|
||||||
EventType::InputReceived,
|
EventType::InputReceived,
|
||||||
|
EventType::SystemClipboardFailure,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,8 +159,12 @@ impl ZellijPlugin for State {
|
||||||
Event::CopyToClipboard => {
|
Event::CopyToClipboard => {
|
||||||
self.diplay_text_copied_hint = true;
|
self.diplay_text_copied_hint = true;
|
||||||
}
|
}
|
||||||
|
Event::SystemClipboardFailure => {
|
||||||
|
self.display_system_clipboard_failure = true;
|
||||||
|
}
|
||||||
Event::InputReceived => {
|
Event::InputReceived => {
|
||||||
self.diplay_text_copied_hint = false;
|
self.diplay_text_copied_hint = false;
|
||||||
|
self.display_system_clipboard_failure = false;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -188,12 +195,16 @@ impl ZellijPlugin for State {
|
||||||
if t.is_fullscreen_active {
|
if t.is_fullscreen_active {
|
||||||
second_line = if self.diplay_text_copied_hint {
|
second_line = if self.diplay_text_copied_hint {
|
||||||
text_copied_hint(&self.mode_info.palette)
|
text_copied_hint(&self.mode_info.palette)
|
||||||
|
} else if self.display_system_clipboard_failure {
|
||||||
|
system_clipboard_error(&self.mode_info.palette)
|
||||||
} else {
|
} else {
|
||||||
fullscreen_panes_to_hide(&self.mode_info.palette, t.panes_to_hide)
|
fullscreen_panes_to_hide(&self.mode_info.palette, t.panes_to_hide)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
second_line = if self.diplay_text_copied_hint {
|
second_line = if self.diplay_text_copied_hint {
|
||||||
text_copied_hint(&self.mode_info.palette)
|
text_copied_hint(&self.mode_info.palette)
|
||||||
|
} else if self.display_system_clipboard_failure {
|
||||||
|
system_clipboard_error(&self.mode_info.palette)
|
||||||
} else {
|
} else {
|
||||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||||
}
|
}
|
||||||
|
|
@ -203,6 +214,8 @@ impl ZellijPlugin for State {
|
||||||
if t.is_fullscreen_active {
|
if t.is_fullscreen_active {
|
||||||
second_line = if self.diplay_text_copied_hint {
|
second_line = if self.diplay_text_copied_hint {
|
||||||
text_copied_hint(&self.mode_info.palette)
|
text_copied_hint(&self.mode_info.palette)
|
||||||
|
} else if self.display_system_clipboard_failure {
|
||||||
|
system_clipboard_error(&self.mode_info.palette)
|
||||||
} else {
|
} else {
|
||||||
locked_fullscreen_panes_to_hide(
|
locked_fullscreen_panes_to_hide(
|
||||||
&self.mode_info.palette,
|
&self.mode_info.palette,
|
||||||
|
|
@ -212,6 +225,8 @@ impl ZellijPlugin for State {
|
||||||
} else {
|
} else {
|
||||||
second_line = if self.diplay_text_copied_hint {
|
second_line = if self.diplay_text_copied_hint {
|
||||||
text_copied_hint(&self.mode_info.palette)
|
text_copied_hint(&self.mode_info.palette)
|
||||||
|
} else if self.display_system_clipboard_failure {
|
||||||
|
system_clipboard_error(&self.mode_info.palette)
|
||||||
} else {
|
} else {
|
||||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +235,8 @@ impl ZellijPlugin for State {
|
||||||
_ => {
|
_ => {
|
||||||
second_line = if self.diplay_text_copied_hint {
|
second_line = if self.diplay_text_copied_hint {
|
||||||
text_copied_hint(&self.mode_info.palette)
|
text_copied_hint(&self.mode_info.palette)
|
||||||
|
} else if self.display_system_clipboard_failure {
|
||||||
|
system_clipboard_error(&self.mode_info.palette)
|
||||||
} else {
|
} else {
|
||||||
keybinds(&self.mode_info, &self.tip_name, cols)
|
keybinds(&self.mode_info, &self.tip_name, cols)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,18 @@ pub fn text_copied_hint(palette: &Palette) -> LinePart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn system_clipboard_error(palette: &Palette) -> LinePart {
|
||||||
|
let hint = " Error using the system clipboard.";
|
||||||
|
let red_color = match palette.red {
|
||||||
|
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||||
|
PaletteColor::EightBit(color) => Fixed(color),
|
||||||
|
};
|
||||||
|
LinePart {
|
||||||
|
part: Style::new().fg(red_color).bold().paint(hint).to_string(),
|
||||||
|
len: hint.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart {
|
pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart {
|
||||||
let white_color = match palette.white {
|
let white_color = match palette.white {
|
||||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,7 @@ pub(crate) struct Screen {
|
||||||
colors: Palette,
|
colors: Palette,
|
||||||
draw_pane_frames: bool,
|
draw_pane_frames: bool,
|
||||||
session_is_mirrored: bool,
|
session_is_mirrored: bool,
|
||||||
|
copy_command: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Screen {
|
impl Screen {
|
||||||
|
|
@ -204,6 +205,7 @@ impl Screen {
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
draw_pane_frames: bool,
|
draw_pane_frames: bool,
|
||||||
session_is_mirrored: bool,
|
session_is_mirrored: bool,
|
||||||
|
copy_command: Option<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Screen {
|
Screen {
|
||||||
bus,
|
bus,
|
||||||
|
|
@ -219,6 +221,7 @@ impl Screen {
|
||||||
default_mode_info: mode_info,
|
default_mode_info: mode_info,
|
||||||
draw_pane_frames,
|
draw_pane_frames,
|
||||||
session_is_mirrored,
|
session_is_mirrored,
|
||||||
|
copy_command,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,6 +494,7 @@ impl Screen {
|
||||||
self.connected_clients.clone(),
|
self.connected_clients.clone(),
|
||||||
self.session_is_mirrored,
|
self.session_is_mirrored,
|
||||||
client_id,
|
client_id,
|
||||||
|
self.copy_command.clone(),
|
||||||
);
|
);
|
||||||
tab.apply_layout(layout, new_pids, tab_index, client_id);
|
tab.apply_layout(layout, new_pids, tab_index, client_id);
|
||||||
if self.session_is_mirrored {
|
if self.session_is_mirrored {
|
||||||
|
|
@ -692,6 +696,7 @@ pub(crate) fn screen_thread_main(
|
||||||
),
|
),
|
||||||
draw_pane_frames,
|
draw_pane_frames,
|
||||||
session_is_mirrored,
|
session_is_mirrored,
|
||||||
|
config_options.copy_command,
|
||||||
);
|
);
|
||||||
loop {
|
loop {
|
||||||
let (event, mut err_ctx) = screen
|
let (event, mut err_ctx) = screen
|
||||||
|
|
|
||||||
39
zellij-server/src/tab/copy_command.rs
Normal file
39
zellij-server/src/tab/copy_command.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
pub struct CopyCommand {
|
||||||
|
command: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CopyCommand {
|
||||||
|
pub fn new(command: String) -> Self {
|
||||||
|
let mut command_with_args = command.split(' ').map(String::from);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
command: command_with_args.next().expect("missing command"),
|
||||||
|
args: command_with_args.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set(&self, value: String) -> bool {
|
||||||
|
let process = match 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
match process.stdin.unwrap().write_all(value.as_bytes()) {
|
||||||
|
Err(why) => {
|
||||||
|
eprintln!("couldn't write to {} stdin: {}", self.command, why);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
||||||
//! as well as how they should be resized
|
//! as well as how they should be resized
|
||||||
|
|
||||||
|
mod copy_command;
|
||||||
mod pane_grid;
|
mod pane_grid;
|
||||||
mod pane_resizer;
|
mod pane_resizer;
|
||||||
|
|
||||||
|
use copy_command::CopyCommand;
|
||||||
use zellij_utils::position::{Column, Line};
|
use zellij_utils::position::{Column, Line};
|
||||||
use zellij_utils::{position::Position, serde, zellij_tile};
|
use zellij_utils::{position::Position, serde, zellij_tile};
|
||||||
|
|
||||||
|
|
@ -119,6 +121,7 @@ pub(crate) struct Tab {
|
||||||
session_is_mirrored: bool,
|
session_is_mirrored: bool,
|
||||||
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
|
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
|
||||||
selecting_with_mouse: bool,
|
selecting_with_mouse: bool,
|
||||||
|
copy_command: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
|
|
@ -299,6 +302,7 @@ impl Tab {
|
||||||
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
|
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
|
||||||
session_is_mirrored: bool,
|
session_is_mirrored: bool,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
|
copy_command: Option<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let panes = BTreeMap::new();
|
let panes = BTreeMap::new();
|
||||||
|
|
||||||
|
|
@ -335,6 +339,7 @@ impl Tab {
|
||||||
connected_clients_in_app,
|
connected_clients_in_app,
|
||||||
connected_clients,
|
connected_clients,
|
||||||
selecting_with_mouse: false,
|
selecting_with_mouse: false,
|
||||||
|
copy_command,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1912,11 +1917,20 @@ impl Tab {
|
||||||
|
|
||||||
fn write_selection_to_clipboard(&self, selection: &str) {
|
fn write_selection_to_clipboard(&self, selection: &str) {
|
||||||
let mut output = Output::default();
|
let mut output = Output::default();
|
||||||
|
let mut system_clipboard_failure = false;
|
||||||
output.add_clients(&self.connected_clients);
|
output.add_clients(&self.connected_clients);
|
||||||
output.push_str_to_multiple_clients(
|
match self.copy_command.clone() {
|
||||||
&format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)),
|
Some(copy_command) => {
|
||||||
self.connected_clients.iter().copied(),
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: ideally we should be sending the Render instruction from the screen
|
// TODO: ideally we should be sending the Render instruction from the screen
|
||||||
self.senders
|
self.senders
|
||||||
|
|
@ -1926,7 +1940,11 @@ impl Tab {
|
||||||
.send_to_plugin(PluginInstruction::Update(
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Event::CopyToClipboard,
|
if system_clipboard_failure {
|
||||||
|
Event::SystemClipboardFailure
|
||||||
|
} else {
|
||||||
|
Event::CopyToClipboard
|
||||||
|
},
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||||
let mut connected_clients = HashSet::new();
|
let mut connected_clients = HashSet::new();
|
||||||
connected_clients.insert(client_id);
|
connected_clients.insert(client_id);
|
||||||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||||
|
let copy_command = None;
|
||||||
let mut tab = Tab::new(
|
let mut tab = Tab::new(
|
||||||
index,
|
index,
|
||||||
position,
|
position,
|
||||||
|
|
@ -110,6 +111,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||||
connected_clients,
|
connected_clients,
|
||||||
session_is_mirrored,
|
session_is_mirrored,
|
||||||
client_id,
|
client_id,
|
||||||
|
copy_command,
|
||||||
);
|
);
|
||||||
tab.apply_layout(
|
tab.apply_layout(
|
||||||
LayoutTemplate::default().try_into().unwrap(),
|
LayoutTemplate::default().try_into().unwrap(),
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ fn create_new_screen(size: Size) -> Screen {
|
||||||
let mode_info = ModeInfo::default();
|
let mode_info = ModeInfo::default();
|
||||||
let draw_pane_frames = false;
|
let draw_pane_frames = false;
|
||||||
let session_is_mirrored = true;
|
let session_is_mirrored = true;
|
||||||
|
let copy_command = None;
|
||||||
Screen::new(
|
Screen::new(
|
||||||
bus,
|
bus,
|
||||||
&client_attributes,
|
&client_attributes,
|
||||||
|
|
@ -98,6 +99,7 @@ fn create_new_screen(size: Size) -> Screen {
|
||||||
mode_info,
|
mode_info,
|
||||||
draw_pane_frames,
|
draw_pane_frames,
|
||||||
session_is_mirrored,
|
session_is_mirrored,
|
||||||
|
copy_command,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ pub enum Event {
|
||||||
Mouse(Mouse),
|
Mouse(Mouse),
|
||||||
Timer(f64),
|
Timer(f64),
|
||||||
CopyToClipboard,
|
CopyToClipboard,
|
||||||
|
SystemClipboardFailure,
|
||||||
InputReceived,
|
InputReceived,
|
||||||
Visible(bool),
|
Visible(bool),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,11 @@ pub struct Options {
|
||||||
pub on_force_close: Option<OnForceClose>,
|
pub on_force_close: Option<OnForceClose>,
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub scroll_buffer_size: Option<usize>,
|
pub scroll_buffer_size: Option<usize>,
|
||||||
|
|
||||||
|
/// Switch to using a user supplied command for clipboard instead of OSC52
|
||||||
|
#[structopt(long)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub copy_command: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
|
|
@ -99,6 +104,7 @@ impl Options {
|
||||||
let theme = other.theme.or_else(|| self.theme.clone());
|
let theme = other.theme.or_else(|| self.theme.clone());
|
||||||
let on_force_close = other.on_force_close.or(self.on_force_close);
|
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 scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
|
||||||
|
let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
|
||||||
|
|
||||||
Options {
|
Options {
|
||||||
simplified_ui,
|
simplified_ui,
|
||||||
|
|
@ -111,6 +117,7 @@ impl Options {
|
||||||
mirror_session,
|
mirror_session,
|
||||||
on_force_close,
|
on_force_close,
|
||||||
scroll_buffer_size,
|
scroll_buffer_size,
|
||||||
|
copy_command,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +147,7 @@ impl Options {
|
||||||
let theme = other.theme.or_else(|| self.theme.clone());
|
let theme = other.theme.or_else(|| self.theme.clone());
|
||||||
let on_force_close = other.on_force_close.or(self.on_force_close);
|
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 scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
|
||||||
|
let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
|
||||||
|
|
||||||
Options {
|
Options {
|
||||||
simplified_ui,
|
simplified_ui,
|
||||||
|
|
@ -152,6 +160,7 @@ impl Options {
|
||||||
mirror_session,
|
mirror_session,
|
||||||
on_force_close,
|
on_force_close,
|
||||||
scroll_buffer_size,
|
scroll_buffer_size,
|
||||||
|
copy_command,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,6 +209,7 @@ impl From<CliOptions> for Options {
|
||||||
mirror_session: opts.mirror_session,
|
mirror_session: opts.mirror_session,
|
||||||
on_force_close: opts.on_force_close,
|
on_force_close: opts.on_force_close,
|
||||||
scroll_buffer_size: opts.scroll_buffer_size,
|
scroll_buffer_size: opts.scroll_buffer_size,
|
||||||
|
copy_command: opts.copy_command,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue