feat(copy): add copy_on_select option (#1298)

* feat(copy): add copy_on_select option

with default value of true, keep current behavior of automatically
copying selection to clipboard when done selecting.
with copy_on_select = false, on mouse release the selection remains, and
can be copied with the `Copy` action.

* add example to default.yaml

* add copy action example to default.yaml, cleanup

* fix updated tab tests

* fix selection changing while scrolling after release

* fix clippy warnings
This commit is contained in:
Thomas Linford 2022-04-16 09:04:06 +02:00 committed by GitHub
parent 53afaa01a9
commit 198625b055
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 44 deletions

View file

@ -186,6 +186,36 @@ impl From<&ScreenInstruction> for ScreenContext {
}
}
#[derive(Debug, Clone)]
pub(crate) struct CopyOptions {
pub command: Option<String>,
pub clipboard: Clipboard,
pub copy_on_select: bool,
}
impl CopyOptions {
pub(crate) fn new(
copy_command: Option<String>,
copy_clipboard: Clipboard,
copy_on_select: bool,
) -> Self {
Self {
command: copy_command,
clipboard: copy_clipboard,
copy_on_select,
}
}
#[cfg(test)]
pub(crate) fn default() -> Self {
Self {
command: None,
clipboard: Clipboard::default(),
copy_on_select: true,
}
}
}
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
/// It only directly controls which tab is active, delegating the rest to the individual `Tab`.
pub(crate) struct Screen {
@ -210,13 +240,11 @@ pub(crate) struct Screen {
style: Style,
draw_pane_frames: bool,
session_is_mirrored: bool,
copy_command: Option<String>,
copy_clipboard: Clipboard,
copy_options: CopyOptions,
}
impl Screen {
/// Creates and returns a new [`Screen`].
#[allow(clippy::too_many_arguments)]
pub fn new(
bus: Bus<ScreenInstruction>,
client_attributes: &ClientAttributes,
@ -224,8 +252,7 @@ impl Screen {
mode_info: ModeInfo,
draw_pane_frames: bool,
session_is_mirrored: bool,
copy_command: Option<String>,
copy_clipboard: Clipboard,
copy_options: CopyOptions,
) -> Self {
Screen {
bus,
@ -243,8 +270,7 @@ impl Screen {
default_mode_info: mode_info,
draw_pane_frames,
session_is_mirrored,
copy_command,
copy_clipboard,
copy_options,
}
}
@ -546,8 +572,7 @@ impl Screen {
self.connected_clients.clone(),
self.session_is_mirrored,
client_id,
self.copy_command.clone(),
self.copy_clipboard.clone(),
self.copy_options.clone(),
);
tab.apply_layout(layout, new_pids, tab_index, client_id);
if self.session_is_mirrored {
@ -743,6 +768,11 @@ pub(crate) fn screen_thread_main(
let capabilities = config_options.simplified_ui;
let draw_pane_frames = config_options.pane_frames.unwrap_or(true);
let session_is_mirrored = config_options.mirror_session.unwrap_or(false);
let copy_options = CopyOptions::new(
config_options.copy_command,
config_options.copy_clipboard.unwrap_or_default(),
config_options.copy_on_select.unwrap_or(true),
);
let mut screen = Screen::new(
bus,
@ -757,8 +787,7 @@ pub(crate) fn screen_thread_main(
),
draw_pane_frames,
session_is_mirrored,
config_options.copy_command,
config_options.copy_clipboard.unwrap_or_default(),
copy_options,
);
loop {
let (event, mut err_ctx) = screen

View file

@ -6,10 +6,10 @@ mod copy_command;
use copy_command::CopyCommand;
use zellij_tile::prelude::Style;
use zellij_utils::input::options::Clipboard;
use zellij_utils::position::{Column, Line};
use zellij_utils::{position::Position, serde, zellij_tile};
use crate::screen::CopyOptions;
use crate::ui::pane_boundaries_frame::FrameParams;
use self::clipboard::ClipboardProvider;
@ -89,6 +89,7 @@ pub(crate) struct Tab {
// 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>,
copy_on_select: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -281,8 +282,7 @@ impl Tab {
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
session_is_mirrored: bool,
client_id: ClientId,
copy_command: Option<String>,
copy_clipboard: Clipboard,
copy_options: CopyOptions,
) -> Self {
let name = if name.is_empty() {
format!("Tab #{}", index + 1)
@ -322,9 +322,9 @@ impl Tab {
style,
);
let clipboard_provider = match copy_command {
let clipboard_provider = match copy_options.command {
Some(command) => ClipboardProvider::Command(CopyCommand::new(command)),
None => ClipboardProvider::Osc52(copy_clipboard),
None => ClipboardProvider::Osc52(copy_options.clipboard),
};
Tab {
@ -351,6 +351,7 @@ impl Tab {
link_handler: Rc::new(RefCell::new(LinkHandler::new())),
clipboard_provider,
focus_pane_id: None,
copy_on_select: copy_options.copy_on_select,
}
}
@ -1586,7 +1587,9 @@ impl Tab {
return;
}
// read these here to avoid use of borrowed `*self`, since we are holding active_pane
let selecting = self.selecting_with_mouse;
let copy_on_release = self.copy_on_select;
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
if let Some(active_pane) = active_pane {
@ -1604,11 +1607,15 @@ impl Tab {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
} else if selecting {
active_pane.end_selection(&relative_position, client_id);
if copy_on_release {
let selected_text = active_pane.get_selected_text();
active_pane.reset_selection();
if let Some(selected_text) = selected_text {
self.write_selection_to_clipboard(&selected_text);
}
}
self.selecting_with_mouse = false;
}
}

View file

@ -1,4 +1,5 @@
use super::{Output, Tab};
use crate::screen::CopyOptions;
use crate::zellij_tile::data::{ModeInfo, Palette};
use crate::{
os_input_output::{AsyncReader, Pid, ServerOsApi},
@ -11,7 +12,6 @@ use std::path::PathBuf;
use zellij_tile::prelude::Style;
use zellij_utils::envs::set_session_name;
use zellij_utils::input::layout::LayoutTemplate;
use zellij_utils::input::options::Clipboard;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::Size;
use zellij_utils::position::Position;
@ -102,9 +102,8 @@ fn create_new_tab(size: Size) -> Tab {
let mut connected_clients = HashSet::new();
connected_clients.insert(client_id);
let connected_clients = Rc::new(RefCell::new(connected_clients));
let copy_command = None;
let character_cell_info = Rc::new(RefCell::new(None));
let clipboard = Clipboard::default();
let copy_options = CopyOptions::default();
let mut tab = Tab::new(
index,
position,
@ -120,8 +119,7 @@ fn create_new_tab(size: Size) -> Tab {
connected_clients,
session_is_mirrored,
client_id,
copy_command,
clipboard,
copy_options,
);
tab.apply_layout(
LayoutTemplate::default().try_into().unwrap(),

View file

@ -1,4 +1,5 @@
use super::Tab;
use crate::screen::CopyOptions;
use crate::zellij_tile::data::{ModeInfo, Palette};
use crate::{
os_input_output::{AsyncReader, Pid, ServerOsApi},
@ -10,7 +11,6 @@ use std::convert::TryInto;
use std::path::PathBuf;
use zellij_tile::prelude::Style;
use zellij_utils::input::layout::LayoutTemplate;
use zellij_utils::input::options::Clipboard;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
@ -99,8 +99,7 @@ fn create_new_tab(size: Size) -> Tab {
let character_cell_info = Rc::new(RefCell::new(None));
connected_clients.insert(client_id);
let connected_clients = Rc::new(RefCell::new(connected_clients));
let copy_command = None;
let copy_clipboard = Clipboard::default();
let copy_options = CopyOptions::default();
let mut tab = Tab::new(
index,
position,
@ -116,8 +115,7 @@ fn create_new_tab(size: Size) -> Tab {
connected_clients,
session_is_mirrored,
client_id,
copy_command,
copy_clipboard,
copy_options,
);
tab.apply_layout(
LayoutTemplate::default().try_into().unwrap(),
@ -146,8 +144,7 @@ fn create_new_tab_with_cell_size(
let mut connected_clients = HashSet::new();
connected_clients.insert(client_id);
let connected_clients = Rc::new(RefCell::new(connected_clients));
let copy_command = None;
let copy_clipboard = Clipboard::default();
let copy_options = CopyOptions::default();
let mut tab = Tab::new(
index,
position,
@ -163,8 +160,7 @@ fn create_new_tab_with_cell_size(
connected_clients,
session_is_mirrored,
client_id,
copy_command,
copy_clipboard,
copy_options,
);
tab.apply_layout(
LayoutTemplate::default().try_into().unwrap(),

View file

@ -1,4 +1,4 @@
use super::{Screen, ScreenInstruction};
use super::{CopyOptions, Screen, ScreenInstruction};
use crate::panes::PaneId;
use crate::zellij_tile::data::{ModeInfo, Palette};
use crate::{
@ -10,7 +10,6 @@ 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, SizeInPixels};
@ -92,8 +91,8 @@ fn create_new_screen(size: Size) -> Screen {
let mode_info = ModeInfo::default();
let draw_pane_frames = false;
let session_is_mirrored = true;
let copy_command = None;
let copy_clipboard = Clipboard::default();
let copy_options = CopyOptions::default();
Screen::new(
bus,
&client_attributes,
@ -101,8 +100,7 @@ fn create_new_screen(size: Size) -> Screen {
mode_info,
draw_pane_frames,
session_is_mirrored,
copy_command,
copy_clipboard,
copy_options,
)
}

View file

@ -42,6 +42,9 @@ keybinds:
key: [ Alt: '+']
- action: [Resize: Decrease,]
key: [ Alt: '-']
# uncomment this and adjust key if using copy_on_select=false
# - action: [Copy: ]
# key: [ Alt: 'c']
locked:
- action: [SwitchToMode: Normal,]
key: [Ctrl: 'g',]
@ -316,6 +319,9 @@ keybinds:
key: [ Alt: '+']
- action: [Resize: Decrease,]
key: [ Alt: '-']
# uncomment this and adjust key if using copy_on_select=false
# - action: [Copy: ]
# key: [ Alt: 'c']
renametab:
- action: [SwitchToMode: Normal,]
key: [Char: "\n", Ctrl: 'c', Esc]
@ -536,4 +542,7 @@ plugins:
# Options:
# - system (default)
# - primary
# copy_clipboard: primary
#copy_clipboard: primary
# Enable or disable automatic copy (and clear) of selection when releasing mouse
#copy_on_select: true

View file

@ -84,9 +84,14 @@ pub struct Options {
#[clap(long, arg_enum, ignore_case = true, conflicts_with = "copy-command")]
#[serde(default)]
pub copy_clipboard: Option<Clipboard>,
/// Automatically copy when selecting text (true or false)
#[clap(long)]
#[serde(default)]
pub copy_on_select: Option<bool>,
}
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
pub enum Clipboard {
#[serde(alias = "system")]
System,
@ -124,7 +129,8 @@ 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());
let copy_clipboard = other.copy_clipboard.or(self.copy_clipboard);
let copy_on_select = other.copy_on_select.or(self.copy_on_select);
Options {
simplified_ui,
@ -139,6 +145,7 @@ impl Options {
scroll_buffer_size,
copy_command,
copy_clipboard,
copy_on_select,
}
}
@ -169,7 +176,8 @@ 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());
let copy_clipboard = other.copy_clipboard.or(self.copy_clipboard);
let copy_on_select = other.copy_on_select.or(self.copy_on_select);
Options {
simplified_ui,
@ -184,6 +192,7 @@ impl Options {
scroll_buffer_size,
copy_command,
copy_clipboard,
copy_on_select,
}
}
@ -234,6 +243,7 @@ impl From<CliOptions> for Options {
scroll_buffer_size: opts.scroll_buffer_size,
copy_command: opts.copy_command,
copy_clipboard: opts.copy_clipboard,
copy_on_select: opts.copy_on_select,
}
}
}