reedline/core_editor/
clip_buffer.rs

1/// Defines an interface to interact with a Clipboard for cut and paste.
2///
3/// Mutable reference requirements are stricter than always necessary, but the currently used system clipboard API demands them for exclusive access.
4pub trait Clipboard: Send {
5    fn set(&mut self, content: &str, mode: ClipboardMode);
6
7    fn get(&mut self) -> (String, ClipboardMode);
8
9    fn clear(&mut self) {
10        self.set("", ClipboardMode::Normal);
11    }
12
13    fn len(&mut self) -> usize {
14        self.get().0.len()
15    }
16}
17
18/// Determines how the content in the clipboard should be inserted
19#[derive(Copy, Clone, Debug, Default)]
20pub enum ClipboardMode {
21    /// As direct content at the current cursor position
22    #[default]
23    Normal,
24    /// As new lines below or above
25    Lines,
26}
27
28/// Simple buffer that provides a clipboard only usable within the application/library.
29#[derive(Default)]
30pub struct LocalClipboard {
31    content: String,
32    mode: ClipboardMode,
33}
34
35impl LocalClipboard {
36    #[allow(dead_code)]
37    pub fn new() -> Self {
38        Self::default()
39    }
40}
41
42impl Clipboard for LocalClipboard {
43    fn set(&mut self, content: &str, mode: ClipboardMode) {
44        self.content = content.to_owned();
45        self.mode = mode;
46    }
47
48    fn get(&mut self) -> (String, ClipboardMode) {
49        (self.content.clone(), self.mode)
50    }
51}
52
53#[cfg(feature = "system_clipboard")]
54pub use system_clipboard::SystemClipboard;
55
56#[cfg(feature = "system_clipboard")]
57/// Helper to get a clipboard based on the `system_clipboard` feature flag:
58///
59/// Enabled -> [`SystemClipboard`], which talks to the system
60///
61/// Disabled -> [`LocalClipboard`], which supports cutting and pasting limited to the [`crate::Reedline`] instance
62pub fn get_default_clipboard() -> SystemClipboard {
63    SystemClipboard::new()
64}
65
66#[cfg(not(feature = "system_clipboard"))]
67/// Helper to get a clipboard based on the `system_clipboard` feature flag:
68///
69/// Enabled -> `SystemClipboard`, which talks to the system
70///
71/// Disabled -> [`LocalClipboard`], which supports cutting and pasting limited to the [`crate::Reedline`] instance
72pub fn get_default_clipboard() -> LocalClipboard {
73    LocalClipboard::new()
74}
75
76#[cfg(feature = "system_clipboard")]
77mod system_clipboard {
78    use super::*;
79    use clipboard::{ClipboardContext, ClipboardProvider};
80
81    /// Wrapper around [`clipboard`](https://docs.rs/clipboard) crate
82    ///
83    /// Requires that the feature `system_clipboard` is enabled
84    pub struct SystemClipboard {
85        cb: ClipboardContext,
86        local_copy: String,
87        mode: ClipboardMode,
88    }
89
90    impl SystemClipboard {
91        pub fn new() -> Self {
92            let cb = ClipboardProvider::new().unwrap();
93            SystemClipboard {
94                cb,
95                local_copy: String::new(),
96                mode: ClipboardMode::Normal,
97            }
98        }
99    }
100
101    impl Clipboard for SystemClipboard {
102        fn set(&mut self, content: &str, mode: ClipboardMode) {
103            self.local_copy = content.to_owned();
104            let _ = self.cb.set_contents(content.to_owned());
105            self.mode = mode;
106        }
107
108        fn get(&mut self) -> (String, ClipboardMode) {
109            let system_content = self.cb.get_contents().unwrap_or_default();
110            if system_content == self.local_copy {
111                // We assume the content was yanked inside the line editor and the last yank determined the mode.
112                (system_content, self.mode)
113            } else {
114                // Content has changed, default to direct insertion.
115                (system_content, ClipboardMode::Normal)
116            }
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::{get_default_clipboard, Clipboard, ClipboardMode};
124    #[test]
125    fn reads_back() {
126        let mut cb = get_default_clipboard();
127        // If the system clipboard is used we want to persist it for the user
128        let previous_state = cb.get().0;
129
130        // Actual test
131        cb.set("test", ClipboardMode::Normal);
132        assert_eq!(cb.len(), 4);
133        assert_eq!(cb.get().0, "test".to_owned());
134        cb.clear();
135        assert_eq!(cb.get().0, String::new());
136
137        // Restore!
138
139        cb.set(&previous_state, ClipboardMode::Normal);
140    }
141}